Skip to content

Commit 71b7c24

Browse files
feat: improvements
1 parent 03de8da commit 71b7c24

12 files changed

Lines changed: 402 additions & 3 deletions

File tree

pythoncms/app.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,45 @@ def shopyo_upload():
357357
except AttributeError as e:
358358
pass
359359

360+
@click.command("update-password")
361+
@click.argument("email")
362+
@click.argument("password")
363+
@with_appcontext
364+
def update_password(email, password):
365+
from shopyo_auth.models import User
366+
367+
user = User.query.filter_by(email=email).first()
368+
if user:
369+
user.password = password
370+
db.session.commit()
371+
click.echo(f"Password updated for {email}")
372+
else:
373+
click.echo(f"User {email} not found")
374+
375+
@click.command("add-user")
376+
@click.argument("email")
377+
@click.argument("password")
378+
@click.option("--admin", is_flag=True, help="Create as admin")
379+
@with_appcontext
380+
def add_user(email, password, admin):
381+
from shopyo_auth.models import User
382+
383+
user = User.query.filter_by(email=email).first()
384+
if user:
385+
click.echo(f"User {email} already exists")
386+
else:
387+
new_user = User()
388+
new_user.email = email
389+
new_user.password = password
390+
new_user.is_email_confirmed = True
391+
if admin:
392+
new_user.is_admin = True
393+
db.session.add(new_user)
394+
db.session.commit()
395+
click.echo(f"User {email} created")
396+
360397
app.cli.add_command(shopyo_upload)
398+
app.cli.add_command(update_password)
399+
app.cli.add_command(add_user)
361400

362401

pythoncms/migrations/versions/0fbf6b2e734f_.py renamed to pythoncms/migrations/versions/a5c9bf62cffd_.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""empty message
22
3-
Revision ID: 0fbf6b2e734f
3+
Revision ID: a5c9bf62cffd
44
Revises:
5-
Create Date: 2025-05-06 23:12:32.784695
5+
Create Date: 2026-01-05 15:46:04.761454
66
77
"""
88
from alembic import op
99
import sqlalchemy as sa
1010

1111

1212
# revision identifiers, used by Alembic.
13-
revision = '0fbf6b2e734f'
13+
revision = 'a5c9bf62cffd'
1414
down_revision = None
1515
branch_labels = None
1616
depends_on = None
@@ -26,6 +26,15 @@ def upgrade():
2626
sa.Column('message', sa.String(length=1024), nullable=True),
2727
sa.PrimaryKeyConstraint('id')
2828
)
29+
op.create_table('content_types',
30+
sa.Column('id', sa.Integer(), nullable=False),
31+
sa.Column('name', sa.String(length=100), nullable=False),
32+
sa.Column('description', sa.String(length=255), nullable=True),
33+
sa.Column('schema', sa.JSON(), nullable=False),
34+
sa.Column('created_at', sa.DateTime(), nullable=True),
35+
sa.PrimaryKeyConstraint('id'),
36+
sa.UniqueConstraint('name')
37+
)
2938
op.create_table('i18n_records',
3039
sa.Column('id', sa.Integer(), nullable=False),
3140
sa.Column('strid', sa.String(length=1024), nullable=True),
@@ -65,6 +74,15 @@ def upgrade():
6574
sa.UniqueConstraint('email'),
6675
sa.UniqueConstraint('username')
6776
)
77+
op.create_table('content_items',
78+
sa.Column('id', sa.Integer(), nullable=False),
79+
sa.Column('content_type_id', sa.Integer(), nullable=False),
80+
sa.Column('data', sa.JSON(), nullable=False),
81+
sa.Column('created_at', sa.DateTime(), nullable=True),
82+
sa.Column('updated_at', sa.DateTime(), nullable=True),
83+
sa.ForeignKeyConstraint(['content_type_id'], ['content_types.id'], ),
84+
sa.PrimaryKeyConstraint('id')
85+
)
6886
op.create_table('role_user_bridge',
6987
sa.Column('user_id', sa.Integer(), nullable=False),
7088
sa.Column('role_id', sa.Integer(), nullable=False),
@@ -78,10 +96,12 @@ def upgrade():
7896
def downgrade():
7997
# ### commands auto generated by Alembic - please adjust! ###
8098
op.drop_table('role_user_bridge')
99+
op.drop_table('content_items')
81100
op.drop_table('users')
82101
op.drop_table('settings')
83102
op.drop_table('roles')
84103
op.drop_table('pages')
85104
op.drop_table('i18n_records')
105+
op.drop_table('content_types')
86106
op.drop_table('contact')
87107
# ### end Alembic commands ###

pythoncms/modules/contenttype/forms.py

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
# global templates variables in here
3+
# available_everywhere = {
4+
#
5+
# }
6+
7+
# global configs in here, defined by profile
8+
# configs = {
9+
# "development": {
10+
# "CONFIG_VAR": "DEVVALUE"
11+
# },
12+
# "production": {
13+
# "CONFIG_VAR": "PRODVALUE"
14+
# },
15+
# "testing": {
16+
# "CONFIG_VAR": "TESTVALUE"
17+
# }
18+
# }
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"author": {
3+
"mail": "",
4+
"name": "",
5+
"website": ""
6+
},
7+
"display_string": "Content Types",
8+
"fa-icon": "fa fa-layer-group",
9+
"module_name": "contenttype",
10+
"type": "show",
11+
"url_prefix": "/contenttype",
12+
"dashboard": "/dashboard",
13+
"menu": {
14+
"Manage Types": "/dashboard",
15+
"Create Type": "/create"
16+
}
17+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
from datetime import datetime
2+
from init import db
3+
4+
class ContentType(db.Model):
5+
__tablename__ = "content_types"
6+
id = db.Column(db.Integer, primary_key=True)
7+
name = db.Column(db.String(100), unique=True, nullable=False)
8+
description = db.Column(db.String(255))
9+
# schema will store a list of fields, e.g., [{"name": "title", "type": "text"}, ...]
10+
schema = db.Column(db.JSON, nullable=False)
11+
created_at = db.Column(db.DateTime, default=datetime.utcnow)
12+
13+
items = db.relationship("ContentItem", backref="content_type", lazy=True, cascade="all, delete-orphan")
14+
15+
def insert(self):
16+
db.session.add(self)
17+
db.session.commit()
18+
19+
def update(self):
20+
db.session.commit()
21+
22+
def delete(self):
23+
db.session.delete(self)
24+
db.session.commit()
25+
26+
27+
class ContentItem(db.Model):
28+
__tablename__ = "content_items"
29+
id = db.Column(db.Integer, primary_key=True)
30+
content_type_id = db.Column(db.Integer, db.ForeignKey("content_types.id"), nullable=False)
31+
# data will store the actual values, e.g., {"title": "Hello", "body": "..."}
32+
data = db.Column(db.JSON, nullable=False)
33+
created_at = db.Column(db.DateTime, default=datetime.utcnow)
34+
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
35+
36+
def insert(self):
37+
db.session.add(self)
38+
db.session.commit()
39+
40+
def update(self):
41+
db.session.commit()
42+
43+
def delete(self):
44+
db.session.delete(self)
45+
db.session.commit()

pythoncms/modules/contenttype/templates/contenttype/blocks/sidebar.html

Whitespace-only changes.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{% extends get_active_back_theme()+'/base.html' %}
2+
3+
{% block content %}
4+
<div class="container-xxl flex-grow-1 container-p-y">
5+
<h4 class="fw-bold py-3 mb-4"><span class="text-muted fw-light">Content /</span> Manage Types</h4>
6+
7+
<div class="card">
8+
<div class="card-header d-flex justify-content-between align-items-center">
9+
<h5 class="mb-0">Content Types</h5>
10+
<a href="{{ url_for('contenttype.create') }}" class="btn btn-primary">Create New Type</a>
11+
</div>
12+
<div class="table-responsive text-nowrap">
13+
<table class="table">
14+
<thead>
15+
<tr>
16+
<th>Name</th>
17+
<th>Description</th>
18+
<th>Fields</th>
19+
<th>Actions</th>
20+
</tr>
21+
</thead>
22+
<tbody class="table-border-bottom-0">
23+
{% for ct in content_types %}
24+
<tr>
25+
<td><strong>{{ ct.name }}</strong></td>
26+
<td>{{ ct.description }}</td>
27+
<td>
28+
{% for field in ct.schema %}
29+
<span class="badge bg-label-info me-1">{{ field.name }} ({{ field.type }})</span>
30+
{% endfor %}
31+
</td>
32+
<td>
33+
<div class="dropdown">
34+
<button type="button" class="btn p-0 dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
35+
<i class="bx bx-dots-vertical-rounded"></i>
36+
</button>
37+
<div class="dropdown-menu">
38+
<a class="dropdown-item" href="{{ url_for('contenttype.items', type_id=ct.id) }}">
39+
<i class="bx bx-list-ul me-1"></i> View Items
40+
</a>
41+
<a class="dropdown-item" href="{{ url_for('contenttype.item_add', type_id=ct.id) }}">
42+
<i class="bx bx-plus me-1"></i> Add Item
43+
</a>
44+
<a class="dropdown-item text-danger" href="{{ url_for('contenttype.delete_type', type_id=ct.id) }}" onclick="return confirm('Are you sure?')">
45+
<i class="bx bx-trash me-1"></i> Delete Type
46+
</a>
47+
</div>
48+
</div>
49+
</td>
50+
</tr>
51+
{% else %}
52+
<tr>
53+
<td colspan="4" class="text-center">No content types defined yet.</td>
54+
</tr>
55+
{% endfor %}
56+
</tbody>
57+
</table>
58+
</div>
59+
</div>
60+
</div>
61+
{% endblock %}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{% extends get_active_back_theme()+'/base.html' %}
2+
3+
{% block content %}
4+
<div class="container-xxl flex-grow-1 container-p-y">
5+
<h4 class="fw-bold py-3 mb-4">
6+
<span class="text-muted fw-light">Content /</span> {{ content_type.name }} Items
7+
</h4>
8+
9+
<div class="card">
10+
<div class="card-header d-flex justify-content-between align-items-center">
11+
<h5 class="mb-0">{{ content_type.name }}</h5>
12+
<a href="{{ url_for('contenttype.item_add', type_id=content_type.id) }}" class="btn btn-primary">Add New Item</a>
13+
</div>
14+
<div class="table-responsive text-nowrap">
15+
<table class="table">
16+
<thead>
17+
<tr>
18+
{% for field in content_type.schema %}
19+
<th>{{ field.name|capitalize }}</th>
20+
{% endfor %}
21+
<th>Created</th>
22+
<th>Actions</th>
23+
</tr>
24+
</thead>
25+
<tbody class="table-border-bottom-0">
26+
{% for item in items %}
27+
<tr>
28+
{% for field in content_type.schema %}
29+
<td>{{ item.data.get(field.name, '')|truncate(50) }}</td>
30+
{% endfor %}
31+
<td>{{ item.created_at.strftime('%Y-%m-%d') }}</td>
32+
<td>
33+
<div class="dropdown">
34+
<button type="button" class="btn p-0 dropdown-toggle hide-arrow" data-bs-toggle="dropdown">
35+
<i class="bx bx-dots-vertical-rounded"></i>
36+
</button>
37+
<div class="dropdown-menu">
38+
<a class="dropdown-item" href="{{ url_for('contenttype.item_edit', item_id=item.id) }}">
39+
<i class="bx bx-edit-alt me-1"></i> Edit
40+
</a>
41+
<a class="dropdown-item text-danger" href="{{ url_for('contenttype.item_delete', item_id=item.id) }}" onclick="return confirm('Are you sure?')">
42+
<i class="bx bx-trash me-1"></i> Delete
43+
</a>
44+
</div>
45+
</div>
46+
</td>
47+
</tr>
48+
{% else %}
49+
<tr>
50+
<td colspan="{{ content_type.schema|length + 2 }}" class="text-center">No items found.</td>
51+
</tr>
52+
{% endfor %}
53+
</tbody>
54+
</table>
55+
</div>
56+
</div>
57+
</div>
58+
{% endblock %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Please add your functional tests to this file.

0 commit comments

Comments
 (0)