Internal/modules (#1)

* Restructured app
* Added Travis CI
* Added 'Deploy to heroku' button
This commit is contained in:
Daniel Grams 2020-11-13 12:24:26 +01:00 committed by GitHub
parent 2434ea24f6
commit d63f340384
231 changed files with 864 additions and 705 deletions

1
.flaskenv Normal file
View File

@ -0,0 +1 @@
FLASK_APP=bootstrap.py

18
.travis.yml Normal file
View File

@ -0,0 +1,18 @@
language: python
python:
- "3.7"
addons:
apt:
packages:
- postgresql-10-postgis-2.5
- postgresql-10-postgis-2.5-scripts
postgresql: '10'
services:
- postgresql
install:
- pip install -r requirements.txt
before_script:
- psql -c 'create database gsevpt_tests;' -U postgres
- psql -c 'create extension postgis;' -U postgres -d gsevpt_tests
script:
- pytest

2
.vscode/launch.json vendored
View File

@ -10,7 +10,7 @@
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_APP": "project",
"FLASK_ENV": "development",
"FLASK_DEBUG": "1"
},

View File

@ -1,2 +1,2 @@
release: python manage.py db upgrade
web: gunicorn app:app --log-file=-
web: gunicorn project:app --log-file=-

110
README.md
View File

@ -1,74 +1,70 @@
[![Build Status](https://travis-ci.com/DanielGrams/gsevpt.svg?branch=master)](https://travis-ci.com/DanielGrams/gsevpt)
# Goslar Event Prototype
Website prototype using Python, Flask and Postgres running on Heroku.
## Setup
## Automatic Deployment
### Environment variables
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Create `.env` file in the root directory and define the following variables:
## Manual Installation
```
DATABASE_URL=
GOOGLE_OAUTH_CLIENT_ID=
GOOGLE_OAUTH_CLIENT_SECRET=
OAUTHLIB_INSECURE_TRANSPORT=true
OAUTHLIB_RELAX_TOKEN_SCOPE=true
GOOGLE_MAPS_API_KEY=
### Requirements
- Python 3.7
- pip
- Postgres with postgis
### Create database
```sh
psql -c 'create database gsevpt;' -U postgres
```
### Install and run
```
```sh
export DATABASE_URL="postgresql://postgres@localhost/gsevpt"
pip install -r requirements.txt
python manage.py db upgrade
flask run --host 0.0.0.0
```
## Configuration
Create `.env` file in the root directory or pass as environment variables.
### Security
| Variable | Function |
| --- | --- |
| SECRET_KEY | A secret key for verifying the integrity of signed cookies. Generate a nice key using secrets.token_urlsafe(). |
| SECURITY_PASSWORD_HASH | Bcrypt is set as default SECURITY_PASSWORD_HASH, which requires a salt. Generate a good salt using: secrets.SystemRandom().getrandbits(128). |
### Send notifications via Mail
| Variable | Function |
| --- | --- |
| MAIL_DEFAULT_SENDER | see <https://pythonhosted.org/Flask-Mail/> |
| MAIL_PASSWORD | " |
| MAIL_PORT | " |
| MAIL_SERVER | " |
| MAIL_USERNAME | " |
### Login with Google via OAuth
| Variable | Function |
| --- | --- |
| GOOGLE_OAUTH_CLIENT_ID | Client Id |
| GOOGLE_OAUTH_CLIENT_SECRET | Secret |
### Resolve addresses with Google Maps
| Variable | Function |
| --- | --- |
| GOOGLE_MAPS_API_KEY | API Key with Places API enabled |
## Development
### Database
```
python manage.py db init
python manage.py db migrate
python manage.py db upgrade
```
#### Local development only
```
python manage.py db history
python manage.py db downgrade
// reset git: migrations/versions
python manage.py db migrate
python manage.py db upgrade
```
### Kill local detached server
```
lsof -i :5000
kill -9 PIDNUMBER
```
### i18n
<https://pythonhosted.org/Flask-BabelEx/>
#### Init
```
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel init -i messages.pot -d translations -l de
```
#### Neue msgid's scannen und in *.po mergen
```
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel update -i messages.pot -d translations
```
#### Nach dem Übersetzen
```
pybabel compile -d translations
```
[Development](doc/development.md)

18
app.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "Goslar Event Prototype",
"description": "Website prototype using Python, Flask and Postgres running on Heroku.",
"image": "heroku/python",
"keywords": ["python" ],
"repository": "https://github.com/DanielGrams/gsevpt",
"addons": [ "heroku-postgresql" ],
"env": {
"SECRET_TOKEN": {
"description": "A secret key for verifying the integrity of signed cookies.",
"generator": "secret"
},
"SECURITY_PASSWORD_SALT": {
"description": "Bcrypt salt for encrypting passwords.",
"generator": "secret"
}
}
}

View File

@ -1,4 +1,4 @@
[ignore: env/**]
[python: **.py]
[jinja2: templates/**.html]
[python: app/**.py]
[jinja2: app/templates/**.html]
extensions=jinja2.ext.autoescape,jinja2.ext.with_

1
bootstrap.py Normal file
View File

@ -0,0 +1 @@
from project import app

52
doc/development.md Normal file
View File

@ -0,0 +1,52 @@
# Development
## Tests
### Create test database
```sh
psql -c 'create database gsevpt_tests;' -U postgres
psql -c 'create extension postgis;' -d gsevpt_tests -U postgres
```
### Run tests
```sh
pytest
```
## Database
### Create new revision
```sh
python manage.py db migrate
```
### Upgrade database
```sh
python manage.py db upgrade
```
## i18n
<https://pythonhosted.org/Flask-BabelEx/>
### Init
```sh
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel init -i messages.pot -d app/translations -l de
```
### Extract new msgid's and merge into *.po files
```sh
pybabel extract -F babel.cfg -o messages.pot . && pybabel extract -F babel.cfg -k lazy_gettext -o messages.pot . && pybabel update -i messages.pot -d app/translations
```
#### Compile after translation is done
```sh
pybabel compile -d app/translations
```

View File

@ -1,6 +1,6 @@
from flask_script import Manager, Command
from flask_migrate import Migrate, MigrateCommand
from app import app, db
from project import app, db
migrate = Migrate(app, db)
manager = Manager(app)

View File

@ -8,7 +8,7 @@ Create Date: ${create_date}
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
${imports if imports else ""}
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-27 20:58:57.392619
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,8 +8,8 @@ Create Date: 2020-10-22 17:59:27.823624
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from models import EventRejectionReason, EventReviewStatus
from project import dbtypes
from project.models import EventRejectionReason, EventReviewStatus
# revision identifiers, used by Alembic.
revision = '021f602d9965'
@ -27,8 +27,8 @@ def upgrade():
sa.Column('start', sa.DateTime(timezone=True), nullable=False),
sa.Column('description', sa.UnicodeText(), nullable=True),
sa.Column('external_link', sa.String(length=255), nullable=True),
sa.Column('review_status', db.IntegerEnum(EventReviewStatus), nullable=True),
sa.Column('rejection_resaon', db.IntegerEnum(EventRejectionReason), nullable=True),
sa.Column('review_status', dbtypes.IntegerEnum(EventReviewStatus), nullable=True),
sa.Column('rejection_resaon', dbtypes.IntegerEnum(EventRejectionReason), nullable=True),
sa.Column('contact_name', sa.Unicode(length=255), nullable=False),
sa.Column('contact_email', sa.Unicode(length=255), nullable=True),
sa.Column('contact_phone', sa.Unicode(length=255), nullable=True),

View File

@ -7,8 +7,9 @@ Create Date: 2020-10-02 09:29:12.932229
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.sql import text
import sqlalchemy_utils
import db
from project import dbtypes
from geoalchemy2.types import Geometry
@ -20,6 +21,10 @@ depends_on = None
def upgrade():
bind = op.get_bind()
bind.execute(text("create extension if not exists postgis;"))
# ### commands auto generated by Alembic - please adjust! ###
#op.drop_table('spatial_ref_sys')
op.add_column('location', sa.Column('coordinate', Geometry(geometry_type='POINT', from_text='ST_GeomFromEWKT', name='geometry'), nullable=True))

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-18 11:55:12.315808
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-11-08 16:14:01.866196
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -9,7 +9,7 @@ from alembic import op
import sqlalchemy as sa
from sqlalchemy import orm
import sqlalchemy_utils
import db
from project import dbtypes
from sqlalchemy.ext.declarative import declarative_base
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-17 19:54:25.703175
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-09-18 15:04:03.359403
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-27 20:31:42.566357
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-09-29 15:38:44.033998
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-13 19:01:04.770613
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-30 13:13:44.694716
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,8 +8,8 @@ Create Date: 2020-08-01 15:43:11.377833
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from models import EventRejectionReason, EventReviewStatus
from project import dbtypes
from project.models import EventRejectionReason, EventReviewStatus
# revision identifiers, used by Alembic.
@ -21,8 +21,8 @@ depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('event', sa.Column('rejection_resaon', db.IntegerEnum(EventRejectionReason), nullable=True))
op.add_column('event', sa.Column('review_status', db.IntegerEnum(EventReviewStatus), nullable=True))
op.add_column('event', sa.Column('rejection_resaon', dbtypes.IntegerEnum(EventRejectionReason), nullable=True))
op.add_column('event', sa.Column('review_status', dbtypes.IntegerEnum(EventReviewStatus), nullable=True))
op.drop_column('event', 'verified')
# ### end Alembic commands ###

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-28 16:29:41.403957
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-01 11:09:16.765736
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-23 15:51:36.330825
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.

View File

@ -8,8 +8,8 @@ Create Date: 2020-09-28 10:38:46.424791
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from models import FeaturedEventRejectionReason, FeaturedEventReviewStatus
from project import dbtypes
from project.models import FeaturedEventRejectionReason, FeaturedEventReviewStatus
# revision identifiers, used by Alembic.
@ -25,8 +25,8 @@ def upgrade():
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('review_status', db.IntegerEnum(FeaturedEventReviewStatus), nullable=True),
sa.Column('rejection_resaon', db.IntegerEnum(FeaturedEventRejectionReason), nullable=True),
sa.Column('review_status', dbtypes.IntegerEnum(FeaturedEventReviewStatus), nullable=True),
sa.Column('rejection_resaon', dbtypes.IntegerEnum(FeaturedEventRejectionReason), nullable=True),
sa.Column('rating', sa.Integer(), nullable=True),
sa.Column('created_by_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ),

View File

@ -8,7 +8,7 @@ Create Date: 2020-09-24 18:53:02.861732
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,8 +8,8 @@ Create Date: 2020-10-18 13:06:47.639083
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from models import EventRejectionReason, EventReviewStatus
from project import dbtypes
from project.models import EventRejectionReason, EventReviewStatus
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-17 11:27:53.084732
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-31 16:30:19.185088
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,9 +8,9 @@ Create Date: 2020-09-29 16:53:02.520125
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
from sqlalchemy.dialects import postgresql
from models import EventReferenceRequestRejectionReason, EventReferenceRequestReviewStatus
from project.models import EventReferenceRequestRejectionReason, EventReferenceRequestReviewStatus
# revision identifiers, used by Alembic.
revision = 'a75bd9c8ad3a'
@ -38,8 +38,8 @@ def upgrade():
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('admin_unit_id', sa.Integer(), nullable=False),
sa.Column('review_status', db.IntegerEnum(EventReferenceRequestReviewStatus), nullable=True),
sa.Column('rejection_reason', db.IntegerEnum(EventReferenceRequestRejectionReason), nullable=True),
sa.Column('review_status', dbtypes.IntegerEnum(EventReferenceRequestReviewStatus), nullable=True),
sa.Column('rejection_reason', dbtypes.IntegerEnum(EventReferenceRequestRejectionReason), nullable=True),
sa.Column('created_by_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['admin_unit_id'], ['adminunit.id'], ),
sa.ForeignKeyConstraint(['created_by_id'], ['user.id'], ),

View File

@ -8,7 +8,7 @@ Create Date: 2020-09-25 11:26:03.139800
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-26 15:20:17.685921
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-26 16:08:39.066127
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-26 15:48:47.723256
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-10-04 17:06:54.502012
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-28 17:10:49.606513
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-08 08:53:44.373606
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,8 +8,8 @@ Create Date: 2020-07-07 15:49:58.653888
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from models import EventTargetGroupOrigin, EventAttendanceMode, EventStatus
from project import dbtypes
from project.models import EventTargetGroupOrigin, EventAttendanceMode, EventStatus
# revision identifiers, used by Alembic.
revision = 'f1bc3fa623c7'
@ -23,11 +23,11 @@ def upgrade():
op.add_column('event', sa.Column('accessible_for_free', sa.Boolean(), nullable=True))
op.add_column('event', sa.Column('age_from', sa.Integer(), nullable=True))
op.add_column('event', sa.Column('age_to', sa.Integer(), nullable=True))
op.add_column('event', sa.Column('attendance_mode', db.IntegerEnum(EventAttendanceMode), nullable=True))
op.add_column('event', sa.Column('attendance_mode', dbtypes.IntegerEnum(EventAttendanceMode), nullable=True))
op.add_column('event', sa.Column('kid_friendly', sa.Boolean(), nullable=True))
op.add_column('event', sa.Column('status', db.IntegerEnum(EventStatus), nullable=True))
op.add_column('event', sa.Column('status', dbtypes.IntegerEnum(EventStatus), nullable=True))
op.add_column('event', sa.Column('tags', sa.UnicodeText(), nullable=True))
op.add_column('event', sa.Column('target_group_origin', db.IntegerEnum(EventTargetGroupOrigin), nullable=True))
op.add_column('event', sa.Column('target_group_origin', dbtypes.IntegerEnum(EventTargetGroupOrigin), nullable=True))
# ### end Alembic commands ###

View File

@ -8,7 +8,7 @@ Create Date: 2020-09-18 15:27:37.608869
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -8,7 +8,7 @@ Create Date: 2020-07-17 19:51:08.457429
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
import db
from project import dbtypes
# revision identifiers, used by Alembic.

View File

@ -4,8 +4,8 @@ from flask import jsonify, Flask, render_template, request, url_for, redirect, a
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import joinedload
from sqlalchemy.sql import asc, func
from sqlalchemy import and_, or_, not_
from flask_security import Security, current_user, auth_required, roles_required, hash_password, SQLAlchemySessionUserDatastore
from sqlalchemy import and_, or_, not_, event
from flask_security import Security, current_user, auth_required, roles_required, SQLAlchemySessionUserDatastore
from flask_security.utils import FsPermNeed
from flask_babelex import Babel, gettext, lazy_gettext, format_datetime, to_user_timezone
from flask_principal import Permission
@ -29,6 +29,8 @@ app.config['SECURITY_EMAIL_SENDER'] = os.getenv("MAIL_DEFAULT_SENDER")
app.config['LANGUAGES'] = ['en', 'de']
app.config['GOOGLE_OAUTH_CLIENT_ID'] = os.getenv('GOOGLE_OAUTH_CLIENT_ID')
app.config['GOOGLE_OAUTH_CLIENT_SECRET'] = os.getenv('GOOGLE_OAUTH_CLIENT_SECRET')
app.config['OAUTHLIB_INSECURE_TRANSPORT'] = True
app.config['OAUTHLIB_RELAX_TOKEN_SCOPE'] = True
# Generate a nice key using secrets.token_urlsafe()
app.config['SECRET_KEY'] = os.environ.get("SECRET_KEY", 'pf9Wkove4IKEAXvy-cQkeDPhv9Cb3Ag-wyJILbq_dFw')
@ -68,30 +70,31 @@ if app.config['MAIL_SUPPRESS_SEND']:
print(message.body)
email_dispatched.connect(log_message)
# create db
# Create db
db = SQLAlchemy(app)
# qr code
QRcode(app)
from jsonld import DateTimeEncoder
# JSON
from project.jsonld import DateTimeEncoder
app.json_encoder = DateTimeEncoder
# Setup Flask-Security
from models import User, Role
from project.models import User, Role
user_datastore = SQLAlchemySessionUserDatastore(db.session, User, Role)
security = Security(app, user_datastore)
# OAuth
from oauth import blueprint
from project.oauth import blueprint
app.register_blueprint(blueprint, url_prefix="/login")
import i10n
import jinja_filters
import init_data
from project.i10n import *
from project.jinja_filters import *
from project.init_data import *
# Routes
from views import (
from project.views import (
admin,
admin_unit,
admin_unit_member,

View File

@ -2,7 +2,7 @@ from flask import abort
from flask_security import current_user
from flask_security.utils import FsPermNeed
from flask_principal import Permission
from models import AdminUnitMember, AdminUnit
from project.models import AdminUnitMember, AdminUnit
def has_current_user_permission(permission):
user_perm = Permission(FsPermNeed(permission))

View File

@ -6,8 +6,8 @@ from wtforms.fields.html5 import EmailField, TelField, URLField
from wtforms.validators import DataRequired, Optional, Regexp
from wtforms.widgets.html5 import ColorInput
import decimal
from models import Location, Image
from .common import FileImageForm
from project.models import Location, Image
from project.forms.common import FileImageForm
class AdminUnitLocationForm(FlaskForm):
street = StringField(lazy_gettext('Street'), validators=[Optional()])

View File

@ -5,8 +5,8 @@ from wtforms import StringField, SubmitField, DecimalField, TextAreaField, FormF
from wtforms.fields.html5 import EmailField, TelField
from wtforms.validators import DataRequired, Optional, Regexp
import decimal
from models import Location
from .widgets import MultiCheckboxField
from project.models import Location
from project.forms.widgets import MultiCheckboxField
class InviteAdminUnitMemberForm(FlaskForm):
email = EmailField(lazy_gettext('Email'), validators=[DataRequired()])

View File

@ -6,9 +6,9 @@ from wtforms import SelectMultipleField, FieldList, RadioField, DateTimeField, S
from wtforms.fields.html5 import DateTimeLocalField, EmailField, URLField
from wtforms.validators import DataRequired, Optional
from wtforms.widgets import html_params, HTMLString
from models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus, Image
from .common import event_rating_choices, Base64ImageForm
from .widgets import CustomDateTimeField, CustomDateField
from project.models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus, Image
from project.forms.common import event_rating_choices, Base64ImageForm
from project.forms.widgets import CustomDateTimeField, CustomDateField
class EventPlaceLocationForm(FlaskForm):
street = StringField(lazy_gettext('Street'), validators=[Optional()])

View File

@ -6,9 +6,9 @@ from wtforms import HiddenField, SelectMultipleField, FieldList, RadioField, Dat
from wtforms.fields.html5 import DateTimeLocalField, EmailField
from wtforms.validators import DataRequired, Optional
from wtforms.widgets import html_params, HTMLString
from models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus
from .common import event_rating_choices, weekday_choices, distance_choices
from .widgets import CustomDateField, MultiCheckboxField
from project.models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus
from project.forms.common import event_rating_choices, weekday_choices, distance_choices
from project.forms.widgets import CustomDateField, MultiCheckboxField
class FindEventDateForm(FlaskForm):
class Meta:

View File

@ -6,8 +6,8 @@ from wtforms.fields.html5 import DateTimeLocalField, EmailField, URLField
from wtforms.validators import DataRequired, Optional
from wtforms.widgets import html_params, HTMLString
import decimal
from models import Location, Image
from .common import FileImageForm
from project.models import Location, Image
from project.forms.common import FileImageForm
class EventPlaceLocationForm(FlaskForm):
street = StringField(lazy_gettext('Street'), validators=[Optional()])

View File

@ -6,10 +6,10 @@ from wtforms import FieldList, RadioField, DateTimeField, StringField, SubmitFie
from wtforms.fields.html5 import DateTimeLocalField, EmailField, TelField, URLField
from wtforms.validators import DataRequired, Optional
from wtforms.widgets import html_params, HTMLString
from models import EventSuggestion, EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus, Image
from .common import event_rating_choices, Base64ImageForm
from .widgets import CustomDateTimeField, CustomDateField, TagSelectField
from .common import event_rating_choices
from project.models import EventSuggestion, EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus, Image
from project.forms.common import event_rating_choices, Base64ImageForm
from project.forms.widgets import CustomDateTimeField, CustomDateField, TagSelectField
from project.forms.common import event_rating_choices
class CreateEventSuggestionForm(FlaskForm):
name = StringField(lazy_gettext('Name'), validators=[DataRequired()], description=lazy_gettext('Enter a short, meaningful name for the event.'))

View File

@ -5,8 +5,8 @@ from wtforms import StringField, SubmitField, DecimalField, TextAreaField, FormF
from wtforms.fields.html5 import EmailField, TelField, URLField
from wtforms.validators import DataRequired, Optional, Regexp
import decimal
from models import Location, Image
from .common import FileImageForm
from project.models import Location, Image
from project.forms.common import FileImageForm
class OrganizerLocationForm(FlaskForm):
street = StringField(lazy_gettext('Street'), validators=[Optional()])

View File

@ -6,9 +6,9 @@ from wtforms import HiddenField, SelectMultipleField, FieldList, RadioField, Dat
from wtforms.fields.html5 import DateTimeLocalField, EmailField
from wtforms.validators import DataRequired, Optional
from wtforms.widgets import html_params, HTMLString
from models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus
from .common import event_rating_choices, weekday_choices, distance_choices
from .widgets import CustomDateField, MultiCheckboxField
from project.models import EventPlace, EventTargetGroupOrigin, EventAttendanceMode, EventStatus, Location, EventOrganizer, EventRejectionReason, EventReviewStatus
from project.forms.common import event_rating_choices, weekday_choices, distance_choices
from project.forms.widgets import CustomDateField, MultiCheckboxField
class PlaningForm(FlaskForm):
class Meta:

View File

@ -2,7 +2,7 @@ from flask_babelex import lazy_gettext, gettext
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, SubmitField
from wtforms.validators import DataRequired
from .common import event_rating_choices
from project.forms.common import event_rating_choices
class CreateEventReferenceForm(FlaskForm):
admin_unit_id = SelectField(lazy_gettext('Admin unit'), validators=[DataRequired()], coerce=int)

View File

@ -2,8 +2,8 @@ from flask_babelex import lazy_gettext, gettext
from flask_wtf import FlaskForm
from wtforms import SelectField, StringField, SubmitField
from wtforms.validators import DataRequired
from .common import event_rating_choices
from models import EventReferenceRequestRejectionReason, EventReferenceRequestReviewStatus
from project.forms.common import event_rating_choices
from project.models import EventReferenceRequestRejectionReason, EventReferenceRequestReviewStatus
class CreateEventReferenceRequestForm(FlaskForm):
admin_unit_id = SelectField(lazy_gettext('Admin unit'), validators=[DataRequired()], coerce=int)

View File

@ -4,7 +4,7 @@ from wtforms.validators import StopValidation
import pytz
from datetime import datetime
from flask_babelex import to_user_timezone, gettext
from dateutils import berlin_tz
from project.dateutils import berlin_tz
class MultiCheckboxField(SelectMultipleField):
widget = ListWidget(prefix_label=False)

BIN
project/gsevpt.sqlite Normal file

Binary file not shown.

BIN
project/gsevpt.sqlite3 Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
from app import app, babel
from project import app, babel
from flask import request
@babel.localeselector

View File

@ -1,7 +1,7 @@
from app import app, db
from services.user import upsert_user_role, add_roles_to_user
from services.admin_unit import upsert_admin_unit_member_role
from models import Location
from project import app, db
from project.services.user import upsert_user_role, add_roles_to_user
from project.services.admin_unit import upsert_admin_unit_member_role
from project.models import Location
@app.before_first_request
def create_initial_data():

View File

@ -1,5 +1,5 @@
from app import app
from utils import get_event_category_name, get_localized_enum_name
from project import app
from project.utils import get_event_category_name, get_localized_enum_name
from urllib.parse import quote_plus
import os
@ -15,9 +15,9 @@ app.jinja_env.filters['quote_plus'] = lambda u: quote_plus(u)
def get_manage_menu_options_context_processor():
def get_manage_menu_options(admin_unit):
from access import has_access
from services.event_suggestion import get_event_reviews_badge_query
from services.reference import get_reference_requests_incoming_badge_query
from project.access import has_access
from project.services.event_suggestion import get_event_reviews_badge_query
from project.services.reference import get_reference_requests_incoming_badge_query
reviews_badge = 0
reference_requests_incoming_badge = get_reference_requests_incoming_badge_query(admin_unit).count()

View File

@ -2,7 +2,7 @@ import datetime
import decimal
from json import JSONEncoder
from flask import url_for
from models import EventAttendanceMode, EventStatus
from project.models import EventAttendanceMode, EventStatus
import pytz
berlin_tz = pytz.timezone('Europe/Berlin')

View File

@ -1,4 +1,4 @@
from app import db
from project import db
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, backref, deferred
@ -11,7 +11,7 @@ from flask_security import UserMixin, RoleMixin
from flask_dance.consumer.storage.sqla import OAuthConsumerMixin
from enum import IntEnum
import datetime
from db import IntegerEnum
from project.dbtypes import IntegerEnum
from geoalchemy2 import Geometry
from sqlalchemy import and_

View File

@ -4,8 +4,8 @@ from flask_dance.contrib.google import make_google_blueprint
from flask_dance.consumer import oauth_authorized, oauth_error
from flask_dance.consumer.storage.sqla import SQLAlchemyStorage
from sqlalchemy.orm.exc import NoResultFound
from models import User, OAuth
from app import db, user_datastore
from project.models import User, OAuth
from project import db, user_datastore
from flask_babelex import gettext
blueprint = make_google_blueprint(

View File

@ -1,4 +1,4 @@
from app import app, db, get_admin_unit, update_event_dates_with_recurrence_rule, upsert_event_category
from project import app, db, get_admin_unit, update_event_dates_with_recurrence_rule, upsert_event_category
from pprint import pprint
import datetime
from dateutil import parser, tz
@ -11,7 +11,7 @@ import json
import re
import unicodedata
import decimal
from models import EventReviewStatus, EventTargetGroupOrigin, Location, Event, EventStatus, EventCategory, EventPlace, EventOrganizer, AdminUnit
from project.models import EventReviewStatus, EventTargetGroupOrigin, Location, Event, EventStatus, EventCategory, EventPlace, EventOrganizer, AdminUnit
from sqlalchemy import and_, or_, not_
berlin_tz = pytz.timezone('Europe/Berlin')

View File

@ -1,4 +1,4 @@
from app import app, db
from project import app, db
from pprint import pprint
import datetime
from dateutil import parser, tz
@ -13,11 +13,11 @@ from flask import jsonify
import re
import unicodedata
import decimal
from models import EventReviewStatus, EventTargetGroupOrigin, Location, Event, EventStatus, EventCategory, EventPlace, EventOrganizer, AdminUnit
from project.models import EventReviewStatus, EventTargetGroupOrigin, Location, Event, EventStatus, EventCategory, EventPlace, EventOrganizer, AdminUnit
from sqlalchemy import and_, or_, not_
from dateutils import berlin_tz
from services.admin_unit import get_admin_unit
from services.event import upsert_event_category, update_event_dates_with_recurrence_rule
from project.dateutils import berlin_tz
from project.services.admin_unit import get_admin_unit
from project.services.event import upsert_event_category, update_event_dates_with_recurrence_rule
admin_unit = get_admin_unit('Harzinfo')
category = upsert_event_category('Other')

View File

@ -1,5 +1,5 @@
from app import db
from models import AdminUnit, AdminUnitMember, AdminUnitMemberRole
from project import db
from project.models import AdminUnit, AdminUnitMember, AdminUnitMemberRole
def upsert_admin_unit(unit_name, short_name = None):
admin_unit = AdminUnit.query.filter_by(name = unit_name).first()

View File

@ -1,5 +1,5 @@
from models import EventReviewStatus, EventCategory, Event, EventDate, EventReference, EventPlace, Location, EventSuggestion
from dateutils import dates_from_recurrence_rule, today, date_add_time, date_set_end_of_day
from project.models import EventReviewStatus, EventCategory, Event, EventDate, EventReference, EventPlace, Location, EventSuggestion
from project.dateutils import dates_from_recurrence_rule, today, date_add_time, date_set_end_of_day
from sqlalchemy import and_, or_, not_, func
from sqlalchemy.sql import extract
from dateutil.relativedelta import relativedelta

View File

@ -1,4 +1,4 @@
from dateutils import today, date_add_time, date_set_end_of_day, form_input_from_date, form_input_to_date
from project.dateutils import today, date_add_time, date_set_end_of_day, form_input_from_date, form_input_to_date
from dateutil.relativedelta import relativedelta
from flask import request

View File

@ -1,4 +1,4 @@
from models import EventReviewStatus, EventSuggestion
from project.models import EventReviewStatus, EventSuggestion
from sqlalchemy import and_
def get_event_reviews_badge_query(admin_unit):

View File

@ -1,4 +1,4 @@
from models import Location
from project.models import Location
def upsert_location(street, postalCode, city, latitude = 0, longitude = 0, state = None):
result = Location.query.filter_by(street = street, postalCode=postalCode, city=city, state=state).first()

View File

@ -1,4 +1,4 @@
from models import EventOrganizer, EventPlace
from project.models import EventOrganizer, EventPlace
from sqlalchemy import and_, or_, not_
from sqlalchemy.sql import asc, func

View File

@ -1,4 +1,4 @@
from models import EventPlace
from project.models import EventPlace
from sqlalchemy.sql import asc, func
def upsert_event_place(admin_unit_id, organizer_id, name):

View File

@ -1,5 +1,5 @@
from app import db
from models import EventReference, EventReferenceRequest, EventReferenceRequestReviewStatus
from project import db
from project.models import EventReference, EventReferenceRequest, EventReferenceRequestReviewStatus
from sqlalchemy import and_, or_, not_
def create_event_reference_for_request(request):

View File

@ -1,4 +1,5 @@
from app import user_datastore
from project import user_datastore
from flask_security import Security, current_user, auth_required, roles_required, hash_password
def upsert_user(email, password="password"):
result = user_datastore.find_user(email=email)

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

Before

Width:  |  Height:  |  Size: 939 KiB

After

Width:  |  Height:  |  Size: 939 KiB

View File

Before

Width:  |  Height:  |  Size: 298 KiB

After

Width:  |  Height:  |  Size: 298 KiB

View File

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 193 KiB

View File

Before

Width:  |  Height:  |  Size: 308 KiB

After

Width:  |  Height:  |  Size: 308 KiB

View File

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

View File

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

View File

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Some files were not shown because too many files have changed in this diff Show More