Compare commits

...

8 Commits

Author SHA1 Message Date
Darks 15a4d38ea0
Ajout des notifications 2019-09-01 12:30:41 +02:00
Darks 0c7c408e40
Ajout d'un fichier de configuration local, non tracé par git 2019-09-01 10:35:37 +02:00
Darks 4868774b96
Test d'intégration continue 2019-09-01 00:32:20 +02:00
Darks f508536805
Détaché le nom de la bdd du fichier de config 2019-08-31 23:00:43 +02:00
Lephe eeaab86d0a forum: improve model relationships (so that it works) 2019-08-24 19:17:13 +02:00
Lephe 11b19af199 forum: provide suitable migrations for the database
First migrate without the foreign key to create the tables, then add the
foreign key in a second migration.

Also removed unneeded imports that caused dependency cycles.

Minor "style" edits with the ambiguous use of super and unnecessary
db.Model inheritance.
2019-08-21 16:50:23 +02:00
Darks 201e961ba2
Ajout des stats sur la durée de chargement 2019-08-20 18:07:16 +02:00
Darks 81c910832b
Ajout des post/thread/comment/etc. 2019-08-20 17:34:00 +02:00
27 changed files with 525 additions and 28 deletions

2
.gitignore vendored
View File

@ -22,6 +22,8 @@ uwsgi.ini
run.sh
# Update script to pull repository from SSH
update.sh
# Config to set up some server specific config
local_config.py
## Wiki

2
V5.py
View File

@ -1,6 +1,6 @@
from app import app, db
from app.models.users import User, Guest, Member, Group, GroupPrivilege
# from app.models.models import Post
from app.models.topic import Topic
@app.shell_context_processor

View File

@ -1,21 +1,34 @@
from flask import Flask
from flask import Flask, g
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from config import Config
import time
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
@app.before_request
def request_time():
g.request_start_time = time.time()
g.request_time = lambda: "%.5fs" % (time.time() - g.request_start_time)
login = LoginManager(app)
login.login_view = 'login'
login.login_message = "Veuillez vous authentifier avant de continuer."
from app import models # IDK why this is here, but it works
from app.models.comment import Comment
from app.models.thread import Thread
from app.models.forum import Forum
from app.models.notification import Notification
from app.routes import index, search, users # To load routes at initialization
from app.routes.account import login, account
from app.routes.account import login, account, notification
from app.routes.admin import index, groups, account, trophies
from app.utils import pluralize # To use pluralize into the templates
from app.utils import is_title

View File

@ -11,6 +11,7 @@
shoutbox-kick shoutbox-ban
unlimited-pms footer-statistics community-login
access-admin-panel edit-account delete-account edit-trophies
delete_notification
-
name: Modérateur
css: "color: green"

26
app/models/comment.py Normal file
View File

@ -0,0 +1,26 @@
from app import db
from app.models.post import Post
class Comment(Post):
__tablename__ = 'comment'
__mapper_args__ = {'polymorphic_identity': __tablename__}
# ID of the associated Post object
id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
# Standalone properties
text = db.Column(db.UnicodeText)
# Relations
thread_id = db.Column(db.Integer, db.ForeignKey('thread.id'),
nullable=False)
thread = db.relationship('Thread', backref='comments',
foreign_keys=thread_id)
# attachement = db.relationship('Attachement', backref='comment')
def __init__(self, author, text, thread):
Post.__init__(author, text)
if isinstance(thread, Thread):
thread = thread.id
self.thread_id = thread

26
app/models/forum.py Normal file
View File

@ -0,0 +1,26 @@
from app import db
class Forum(db.Model):
__tablename__ = 'forum'
id = db.Column(db.Integer, primary_key=True)
# Standalone properties
name = db.Column(db.Unicode(64))
slug = db.Column(db.Unicode(64))
description = db.Column(db.UnicodeText)
# Relationships
parent_id = db.Column(db.Integer, db.ForeignKey('forum.id'), nullable=True)
parent = db.relationship('Forum', backref='sub_forums', remote_side=id,
lazy=True)
# Also [topics] which is provided by a backref from the Topic class
def __init__(self, name, description, priv_prefix):
self.name = name
self.description = description
self.priv_prefix = priv_prefix
def __repr__(self):
return f'<Forum: {self.name}>'

View File

@ -0,0 +1,23 @@
from app import db
from datetime import datetime
class Notification(db.Model):
""" A long-term `flash` notification. It is deleted when watched """
__tablename__ = 'notification'
id = db.Column(db.Integer, primary_key=True)
text = db.Column(db.UnicodeText)
href = db.Column(db.UnicodeText)
date = db.Column(db.DateTime, default=datetime.now())
owner_id = db.Column(db.Integer, db.ForeignKey('member.id'), nullable=False)
def __init__(self, owner_id, text, href=None):
""" Check weather or not the id is valid before creating the notif! """
self.text = text
self.href = href
self.owner_id = owner_id
def __repr__(self):
return f'<Notification to {self.owner.name}: {self.text} ({self.href})>'

View File

@ -1,29 +1,34 @@
from datetime import datetime
from app import db
from app.models.users import *
from app.models.users import User
class Post(db.Model):
""" Content a User can create and publish """
__tablename__ = 'post'
id = db.Column(db.Integer, primary_key=True)
type = db.Column(db.String(20))
__mapper_args__ = {
'polymorphic_identity': __tablename__,
'polymorphic_on': type
}
# Standalone properties
text = db.Column(db.Text(convert_unicode=True))
date_created = db.Column(db.DateTime, default=datetime.now)
date_modified = db.Column(db.DateTime, default=datetime.now)
date_created = db.Column(db.DateTime)
date_modified = db.Column(db.DateTime)
# Relationships
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __init__(self, author, text):
""" Create a Post """
self.text = text
if type(author) == Member:
if isinstance(author, User):
author = author.id
self.author_id = author
self.date_created = datetime.now()
self.date_modified = datetime.now()
def update(self, text):
""" Update a post. Check whether the request sender has the right to do
@ -34,7 +39,7 @@ class Post(db.Model):
def change_ownership(self, new_author):
""" Change ownership of a post. Check whether the request sender has the
right to do this! """
if type(new_author) == Member:
if isinstance(new_author, User):
new_author = new_author.id
self.author_id = new_author

View File

@ -26,7 +26,7 @@ class SpecialPrivilege(db.Model):
self.priv = priv
def __repr__(self):
return f'<Privilege "{self.priv}" of member #{self.mid}>'
return f'<Privilege: {self.priv} of member #{self.mid}>'
# Group: User group, corresponds to a community role and a set of privileges
@ -70,7 +70,7 @@ class Group(db.Model):
return sorted(gp.priv for gp in gps)
def __repr__(self):
return f'<Group "{self.name}">'
return f'<Group: {self.name}>'
# Many-to-many relation for users belonging to groups

35
app/models/thread.py Normal file
View File

@ -0,0 +1,35 @@
from app import db
from app.models.post import Post
from app.models.comment import Comment
from config import V5Config
class Thread(Post):
""" Some thread, such as a topic, program, tutorial """
# Foreign Post object ID
__tablename__ = 'thread'
id = db.Column(db.Integer, db.ForeignKey('post.id'), primary_key=True)
# Identify threads as a type of posts using the table name, and add a
# column to further discriminate types of threads
thread_type = db.Column(db.String(20))
__mapper_args__ = {
'polymorphic_identity': __tablename__,
'polymorphic_on': thread_type
}
# Properties
title = db.Column(db.Unicode(V5Config.THREAD_NAME_MAXLEN))
# Also a relation [comments] populated from the Comment class.
# Relations
top_comment_id = db.Column(db.Integer, db.ForeignKey('comment.id'))
top_comment = db.relationship('Comment', foreign_keys=top_comment_id)
def __init__(self, author, text, title):
""" Create a Thread """
Post.__init__(author, text)
self.title = title
def __repr__(self):
return f'<Thread #{self.id}'

22
app/models/topic.py Normal file
View File

@ -0,0 +1,22 @@
from app import db
from app.models.thread import Thread
class Topic(Thread):
__tablename__ = 'topic'
id = db.Column(db.Integer, db.ForeignKey('thread.id'), primary_key=True)
__mapper_args__ = {'polymorphic_identity': __tablename__}
# Relationships
forum_id = db.Column(db.Integer, db.ForeignKey('forum.id'), nullable=False)
forum = db.relationship('Forum', backref='topics',foreign_keys=forum_id)
def __init__(self, author, text, title, forum):
""" Create a Topic """
Post.__init__(author, text, title)
if isinstance(forum, Forum):
forum = forum.id
self.forum_id = forum
def __repr__(self):
return f'<Topic #{self.id}'

View File

@ -2,11 +2,12 @@ from datetime import date
from app import db
from flask import flash
from flask_login import UserMixin
from app.models.post import Post
from app.models.privs import SpecialPrivilege, Group, GroupMember, \
GroupPrivilege
from app.models.trophies import Trophy, TrophyMember
from app.models.notification import Notification
import app.utils.unicode_names as unicode_names
from app.utils.notify import notify
from config import V5Config
import werkzeug.security
@ -14,8 +15,10 @@ import re
import math
import app
# User: Website user that performs actions on the post
class User(UserMixin, db.Model):
""" Website user that performs actions on the post """
__tablename__ = 'user'
# User ID, should be used to refer to any user. Thea actual user can either
@ -25,7 +28,7 @@ class User(UserMixin, db.Model):
type = db.Column(db.String(30))
# TODO: add good relation
posts = db.relationship('Post', backref="author", lazy=False)
posts = db.relationship('Post', backref="author", lazy=True)
__mapper_args__ = {
'polymorphic_identity': __tablename__,
@ -35,8 +38,10 @@ class User(UserMixin, db.Model):
def __repr__(self):
return f'<User: #{self.id}>'
# Guest: Unregistered user with minimal privileges
class Guest(User, db.Model):
class Guest(User):
""" Unregistered user with minimal privileges """
__tablename__ = 'guest'
__mapper_args__ = {'polymorphic_identity': __tablename__}
@ -52,8 +57,9 @@ class Guest(User, db.Model):
return f'<Guest: {self.username} ({self.ip})>'
# Member: Registered user with full access to the website's services
class Member(User, db.Model):
class Member(User):
""" Registered user with full access to the website's services """
__tablename__ = 'member'
__mapper_args__ = {'polymorphic_identity': __tablename__}
@ -92,6 +98,7 @@ class Member(User, db.Model):
newsletter = db.Column(db.Boolean, default=False)
# Relations
notifications = db.relationship('Notification', backref="owner", lazy=True)
trophies = db.relationship('Trophy', secondary=TrophyMember,
back_populates='owners')
@ -203,6 +210,14 @@ class Member(User, db.Model):
return werkzeug.security.check_password_hash(self.password_hash,
password)
def notify(self, message, href=None):
""" Notify a user with a message.
An hyperlink can be added to redirect to the notification source """
n = Notification(self.id, message, href=href)
db.session.add(n)
db.session.commit()
def add_trophy(self, t):
"""
Add a trophy to the current user. Check whether the request sender has
@ -214,8 +229,7 @@ class Member(User, db.Model):
t = Trophy.query.filter_by(name=t).first()
if t not in self.trophies:
self.trophies.append(t)
# TODO: implement the notification system
# self.notify(f"Vous venez de débloquer le trophée '{t.name}'")
self.notify(f"Vous avez débloqué le trophée '{t.name}'")
def del_trophy(self, t):
"""
@ -225,7 +239,7 @@ class Member(User, db.Model):
if type(t) == int:
t = Trophy.query.get(t)
if type(t) == str:
t = Trophy.query.filter_by(name=name).first()
t = Trophy.query.filter_by(name=t).first()
if t in self.trophies:
self.trophies.remove(t)

View File

@ -0,0 +1,42 @@
from flask import redirect, url_for, request, flash
from flask_login import login_required, current_user
from app import app, db
from app.models.notification import Notification
from app.utils.render import render
@app.route('/notifications', methods=['GET'])
@login_required
def list_notifications():
notifications = current_user.notifications
return render('account/notifications.html', notifications=notifications)
@app.route('/notifications/delete/<id>', methods=['GET'])
@login_required
def delete_notification(id=None):
if type(id) == int:
notification = Notification.query.get(id)
if notification:
# Only current user or admin can delete notifications
if notification.owner_id == current_user.id:
db.session.delete(notification)
db.session.commit()
return redirect(url_for('list_notifications'))
elif 'delete_notification' in current_user.privs:
db.session.delete(notification)
db.session.commit()
# TODO: change this redirection
return redirect(url_for('list_notifications'))
else:
abort(403)
abort(404)
elif id == "all":
for n in current_user.notifications:
db.session.delete(n)
db.session.commit()
return redirect(url_for('list_notifications'))
# TODO: add something to allow an admin to delete all notifs for a user
# with a GET parameter
else:
abort(404)

View File

@ -1,4 +1,5 @@
from flask import flash, redirect, url_for
from flask_login import current_user
from wtforms import BooleanField
from app.utils.priv_required import priv_required
from app.models.users import Member
@ -6,6 +7,7 @@ from app.models.trophies import Trophy
from app.forms.account import AdminUpdateAccountForm, AdminDeleteAccountForm, \
AdminAccountEditTrophyForm
from app.utils.render import render
from app.utils.notify import notify
from app import app, db
@ -47,6 +49,7 @@ def adm_edit_account(user_id):
db.session.merge(user)
db.session.commit()
# TODO: send an email to member saying his account has been modified
user.notify(f"Vos informations personnelles ont été modifiées par {current_user.name}.")
flash('Modifications effectuées', 'ok')
else:
flash('Erreur lors de la modification', 'error')

View File

@ -0,0 +1,33 @@
{% extends "base/base.html" %}
{% block title %}
<h1>Notifications</h1>
{% endblock %}
{% block content %}
<section>
{% if notifications %}
<table>
<tr>
<th>Date</th>
<th>Notification</th>
<th><a href="{{ url_for('delete_notification', id='all') }}">Tout supprimer</a></th>
</tr>
{% for n in notifications %}
<tr>
<td>{{ n.date.strftime('Le %Y-%m-%d à %H:%M') }}</td>
<td>
{% if n.href %}<a href="{{ n.href }}">{% endif %}
{{ n.text }}
{% if n.href %}</a>{% endif %}
</td>
<td><a href="{{ url_for('delete_notification', id=n.id)}}">Supprimer</a>
</tr>
{% endfor %}
</table>
{% else %}
Aucune notification.
{% endif %}
</div>
</section>
{% endblock %}

View File

@ -26,7 +26,7 @@
<code>{{ priv }}</code>
{{- ', ' if not loop.last }}
{% endfor %}</td>
<td><a href="{{ url_for('adm_edit_account', user_id=user.id) }}">Modifier</a></td>
<td style="text-align: center"><a href="{{ url_for('adm_edit_account', user_id=user.id) }}">Modifier</a></td>
</tr>
{% endfor %}
</table>

View File

@ -1,4 +1,8 @@
<footer>
<p>Planète Casio est un site communautaire non affilié à CASIO. Toute reproduction de Planète Casio, même partielle, est interdite.</p>
<p>Les programmes et autres publications présentes sur Planète Casio restent la propriété de leurs auteurs et peuvent être soumis à des licences ou des copyrights.</p>
{% if current_user.is_authenticated and current_user.priv('footer-statistics') %}
<p>Page générée en {{ g.request_time() }}</p>
{% endif %}
<p>Ceci est un environnement de test. Tout contenu peut être supprimé sans avertissement préalable.</p>
</footer>

View File

@ -6,7 +6,7 @@
<a href="{{ url_for('user', username=current_user.name) }}">
{{ current_user.name }}</a>
</h2>
<a href="#">
<a href="{{ url_for('list_notifications') }}">
<svg viewBox="0 0 24 24">
<path fill="#ffffff" d="M20,2A2,2 0 0,1 22,4V16A2,2 0 0,1 20,18H6L2,22V4C2,2.89 2.9,2 4,2H20M4,4V17.17L5.17,16H20V4H4M6,7H18V9H6V7M6,11H15V13H6V11Z"></path>
</svg>Notifications

View File

@ -7,7 +7,7 @@ def is_title(object):
"""
Check if an object is a title
"""
if type(object) == Title:
if isinstance(object, Title):
return "Oui"
else:
return "Non"

20
app/utils/notify.py Normal file
View File

@ -0,0 +1,20 @@
from app import db
from app.models.notification import Notification
# from app.models.users import Member
def notify(user, message, href=None):
""" Notify a user (by id, name or object reference) with a message.
An hyperlink can be added to redirect to the notification source """
# Cuz' duck typing is quite cool
# TODO: maybe abort if no user is found
if type(user) == str:
user = Member.query.filter_by(name=user).first()
if isinstance(user, Member):
user = user.id
if user and Member.query.get(user):
n = Notification(user, message, href=href)
db.session.add(n)
db.session.commit()
else:
print("User not found")

View File

@ -1,10 +1,10 @@
import os
from local_config import DB_NAME
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'a-random-secret-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'postgresql+psycopg2://' + os.environ.get('USER') + ':@/pcv5'
'postgresql+psycopg2://' + os.environ.get('USER') + ':@/' + DB_NAME
SQLALCHEMY_TRACK_MODIFICATIONS = False
UPLOAD_FOLDER = './app/static/avatars'
@ -21,5 +21,5 @@ class V5Config(object):
USER_NAME_MAXLEN = 32
# Minimum password length for new users and new passwords
PASSWORD_MINLEN = 10
# Maximum topic name length
TOPIC_NAME_MAXLEN = 32
# Maximum thread name length
THREAD_NAME_MAXLEN = 32

View File

@ -0,0 +1,48 @@
"""Ajout des posts
Revision ID: 611667e86261
Revises: 87b039db71a5
Create Date: 2019-08-20 11:57:56.053453
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '611667e86261'
down_revision = '87b039db71a5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('post',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('type', sa.String(length=20), nullable=True),
sa.Column('text', sa.Text(_expect_unicode=True), nullable=True),
sa.Column('date_created', sa.DateTime(), nullable=True),
sa.Column('date_modified', sa.DateTime(), nullable=True),
sa.Column('author_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['author_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.drop_table('content')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('content',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('type', sa.VARCHAR(length=20), autoincrement=False, nullable=True),
sa.Column('data', sa.TEXT(), autoincrement=False, nullable=True),
sa.Column('date_created', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('date_modified', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('author_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['author_id'], ['user.id'], name='content_author_id_fkey'),
sa.PrimaryKeyConstraint('id', name='content_pkey')
)
op.drop_table('post')
# ### end Alembic commands ###

View File

@ -0,0 +1,54 @@
"""ajout des classes Comment Thread Forum
Revision ID: 6498631e62c5
Revises: f3f6d7f7fa81
Create Date: 2019-08-21 16:47:15.557948
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '6498631e62c5'
down_revision = 'f3f6d7f7fa81'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('forum',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.Unicode(length=64), nullable=True),
sa.Column('slug', sa.Unicode(length=64), nullable=True),
sa.Column('description', sa.UnicodeText(), nullable=True),
sa.Column('parent_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['parent_id'], ['forum.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('thread',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('thread_type', sa.String(length=20), nullable=True),
sa.Column('title', sa.Unicode(length=32), nullable=True),
sa.Column('top_comment', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['id'], ['post.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('comment',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('text', sa.UnicodeText(), nullable=True),
sa.Column('thread_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['id'], ['post.id'], ),
sa.ForeignKeyConstraint(['thread_id'], ['thread.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('comment')
op.drop_table('thread')
op.drop_table('forum')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""add-foreign-key-on-Thread
Revision ID: 794d44c2bef8
Revises: 6498631e62c5
Create Date: 2019-08-21 16:48:06.623266
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '794d44c2bef8'
down_revision = '6498631e62c5'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_foreign_key(None, 'thread', 'comment', ['top_comment'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'thread', type_='foreignkey')
# ### end Alembic commands ###

View File

@ -0,0 +1,34 @@
"""improve relations for threads and comments
Revision ID: e3b140752719
Revises: 794d44c2bef8
Create Date: 2019-08-24 19:09:46.981771
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'e3b140752719'
down_revision = '794d44c2bef8'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('thread', sa.Column('top_comment_id', sa.Integer(), nullable=True))
op.drop_constraint('thread_top_comment_fkey', 'thread', type_='foreignkey')
op.create_foreign_key(None, 'thread', 'comment', ['top_comment_id'], ['id'])
op.drop_column('thread', 'top_comment')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('thread', sa.Column('top_comment', sa.INTEGER(), autoincrement=False, nullable=True))
op.drop_constraint(None, 'thread', type_='foreignkey')
op.create_foreign_key('thread_top_comment_fkey', 'thread', 'comment', ['top_comment'], ['id'])
op.drop_column('thread', 'top_comment_id')
# ### end Alembic commands ###

View File

@ -0,0 +1,36 @@
"""Ajout des notifications
Revision ID: ebca7362eb22
Revises: e3b140752719
Create Date: 2019-09-01 11:36:25.962212
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ebca7362eb22'
down_revision = 'e3b140752719'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('notification',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('text', sa.UnicodeText(), nullable=True),
sa.Column('href', sa.UnicodeText(), nullable=True),
sa.Column('date', sa.DateTime(), nullable=True),
sa.Column('owner_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['owner_id'], ['member.id'], ),
sa.PrimaryKeyConstraint('id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('notification')
# ### end Alembic commands ###

View File

@ -0,0 +1,28 @@
"""Ajout des topics/comments/autres
Revision ID: f3f6d7f7fa81
Revises: 611667e86261
Create Date: 2019-08-20 17:21:10.330435
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'f3f6d7f7fa81'
down_revision = '611667e86261'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('post', 'text')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('post', sa.Column('text', sa.TEXT(), autoincrement=False, nullable=True))
# ### end Alembic commands ###