[Docs] Add/update docstrings and license text in every file

This commit is contained in:
Gergely Polonkai 2018-07-03 08:22:58 +02:00
parent a5bf841898
commit 2f66fdb83e
8 changed files with 230 additions and 2 deletions

View File

@ -14,6 +14,9 @@
# You should have received a copy of the GNU Affero General Public License # You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>. # along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Main module for the Calendar.social app
"""
from datetime import datetime from datetime import datetime
from functools import wraps from functools import wraps
import os import os
@ -40,6 +43,9 @@ def get_locale():
def template_vars(): def template_vars():
"""Function to inject global template variables
"""
now = datetime.utcnow() now = datetime.utcnow()
return { return {
@ -50,6 +56,12 @@ def template_vars():
def route(*args, **kwargs): def route(*args, **kwargs):
"""Mark a function as a future route
Such functions will be iterated over when the application is initialised. ``*args`` and
``**kwargs`` will be passed verbatim to `Flask.route()`.
"""
def decorator(func): def decorator(func):
setattr(func, 'routing', (args, kwargs)) setattr(func, 'routing', (args, kwargs))
@ -59,6 +71,9 @@ def route(*args, **kwargs):
class CalendarSocialApp(Flask): class CalendarSocialApp(Flask):
"""The Calendar.social app
"""
def __init__(self, name, config=None): def __init__(self, name, config=None):
from .models import db, User, Role from .models import db, User, Role
from .security import security from .security import security
@ -93,6 +108,12 @@ class CalendarSocialApp(Flask):
@route('/') @route('/')
def hello(self): def hello(self):
"""View for the main page
This will display a welcome message for users not logged in; for others, their main
calendar view is displayed.
"""
from .calendar_system.gregorian import GregorianCalendar from .calendar_system.gregorian import GregorianCalendar
if not current_user.is_authenticated: if not current_user.is_authenticated:
@ -109,6 +130,12 @@ class CalendarSocialApp(Flask):
@route('/register', methods=['POST', 'GET']) @route('/register', methods=['POST', 'GET'])
def register(self): def register(self):
"""View for user registration
If the ``REGISTRATION_FAILED`` configuration value is set to ``True`` it displays the
registration disabled template. Otherwise, it performs user registration.
"""
if not current_app.config['REGISTRATION_ENABLED']: if not current_app.config['REGISTRATION_ENABLED']:
return render_template('registration-disabled.html') return render_template('registration-disabled.html')
@ -132,6 +159,11 @@ class CalendarSocialApp(Flask):
@route('/new-event', methods=['GET', 'POST']) @route('/new-event', methods=['GET', 'POST'])
@login_required @login_required
def new_event(self): def new_event(self):
"""View for creating a new event
This presents a form to the user that allows entering event details.
"""
from .forms import EventForm from .forms import EventForm
from .models import db, Event from .models import db, Event

View File

@ -1,3 +1,6 @@
"""Main module for Calendar.social so the app can be run directly.
"""
from calsocial import CalendarSocialApp from calsocial import CalendarSocialApp

View File

@ -1,15 +1,48 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Calendar system base definition for Calendar.social
"""
class CalendarSystem: class CalendarSystem:
"""Base class for calendar systems
"""
__has_months__ = False __has_months__ = False
__has_weeks__ = False __has_weeks__ = False
@property @property
def day_names(self): def day_names(self):
"""An iterable of day names
"""
raise NotImplementedError() raise NotImplementedError()
@property @property
def month(self): def month(self):
"""The name of the current month, if applicable
It may include the year.
"""
raise NotImplementedError() raise NotImplementedError()
@property @property
def days(self): def days(self):
"""An iterable of days to be displayed on the month view, if applicable
"""
raise NotImplementedError() raise NotImplementedError()

View File

@ -1,3 +1,22 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Gregorian calendar system for Calendar.social
"""
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import wraps from functools import wraps
@ -7,6 +26,9 @@ from . import CalendarSystem
def to_timestamp(func): def to_timestamp(func):
"""Decorator that converts the return value of a function from `datetime` to a UNIX timestamp
"""
@wraps(func) @wraps(func)
def decorator(*args, **kwargs): def decorator(*args, **kwargs):
return func(*args, **kwargs).timestamp() return func(*args, **kwargs).timestamp()
@ -15,6 +37,9 @@ def to_timestamp(func):
class GregorianCalendar(CalendarSystem): class GregorianCalendar(CalendarSystem):
"""Gregorian calendar system for Calendar.social
"""
__name__ = _('Gregorian') __name__ = _('Gregorian')
__has_months__ = True __has_months__ = True
@ -75,15 +100,24 @@ class GregorianCalendar(CalendarSystem):
@property @property
@to_timestamp @to_timestamp
def prev_year(self): def prev_year(self):
"""Returns the timestamp of the same date in the previous year
"""
return self.timestamp.replace(year=self.timestamp.year - 1) return self.timestamp.replace(year=self.timestamp.year - 1)
@property @property
def prev_year_year(self): def prev_year_year(self):
"""The number of the previous year
"""
return self.timestamp.replace(year=self.timestamp.year - 1).year return self.timestamp.replace(year=self.timestamp.year - 1).year
@property @property
@to_timestamp @to_timestamp
def prev_month(self): def prev_month(self):
"""Returns the timestamp of the same day in the previous month
"""
if self.timestamp.month == 1: if self.timestamp.month == 1:
return self.prev_year.replace(month=12) return self.prev_year.replace(month=12)
@ -91,6 +125,9 @@ class GregorianCalendar(CalendarSystem):
@property @property
def prev_month_name(self): def prev_month_name(self):
"""The name of the previous month
"""
if self.timestamp.month == 1: if self.timestamp.month == 1:
timestamp = self.prev_year.replace(month=12) timestamp = self.prev_year.replace(month=12)
else: else:
@ -101,6 +138,9 @@ class GregorianCalendar(CalendarSystem):
@property @property
@to_timestamp @to_timestamp
def next_month(self): def next_month(self):
"""Returns the timestamp of the same day in the next month
"""
if self.timestamp.month == 12: if self.timestamp.month == 12:
return self.next_year.replace(month=1) return self.next_year.replace(month=1)
@ -108,6 +148,9 @@ class GregorianCalendar(CalendarSystem):
@property @property
def next_month_name(self): def next_month_name(self):
"""The name of the next month
"""
if self.timestamp.month == 12: if self.timestamp.month == 12:
timestamp = self.prev_year.replace(month=1) timestamp = self.prev_year.replace(month=1)
else: else:
@ -118,14 +161,23 @@ class GregorianCalendar(CalendarSystem):
@property @property
@to_timestamp @to_timestamp
def next_year(self): def next_year(self):
"""Returns the timestamp of the same date in the next year
"""
return self.timestamp.replace(year=self.timestamp.year + 1) return self.timestamp.replace(year=self.timestamp.year + 1)
@property @property
def next_year_year(self): def next_year_year(self):
"""The number of the next year
"""
return self.timestamp.replace(year=self.timestamp.year + 1).year return self.timestamp.replace(year=self.timestamp.year + 1).year
@property @property
def has_today(self): def has_today(self):
"""``True`` if the set month holds todays date
"""
now = datetime.utcnow() now = datetime.utcnow()
month_start_timestamp = self.timestamp.replace(day=1, month_start_timestamp = self.timestamp.replace(day=1,
hour=0, hour=0,
@ -144,6 +196,9 @@ class GregorianCalendar(CalendarSystem):
@staticmethod @staticmethod
def day_events(date, user=None): def day_events(date, user=None):
"""Returns all events for a given day
"""
from ..models import Event from ..models import Event
events = Event.query events = Event.query

View File

@ -1,5 +1,9 @@
"""Configuration file for the development environment
"""
DEBUG = True DEBUG = True
ENV = 'dev' ENV = 'dev'
#: If ``True``, registration on the site is enabled.
REGISTRATION_ENABLED = True REGISTRATION_ENABLED = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///local.db' SQLALCHEMY_DATABASE_URI = 'sqlite:///local.db'

View File

@ -1,3 +1,22 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Forms for Calendar.social
"""
from flask_babelex import lazy_gettext as _ from flask_babelex import lazy_gettext as _
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
import pytz import pytz
@ -8,17 +27,32 @@ from wtforms.widgets import TextArea
class RegistrationForm(FlaskForm): class RegistrationForm(FlaskForm):
"""Registration form
"""
username = StringField(_('Username'), validators=[DataRequired()]) username = StringField(_('Username'), validators=[DataRequired()])
email = StringField(_('Email address'), validators=[Email()]) email = StringField(_('Email address'), validators=[Email()])
password = PasswordField(_('Password'), validators=[DataRequired()]) password = PasswordField(_('Password'), validators=[DataRequired()])
password_retype = PasswordField(_('Password, once more'), validators=[DataRequired()]) password_retype = PasswordField(_('Password, once more'), validators=[DataRequired()])
def validate_password_retype(self, field): def validate_password_retype(self, field):
"""Validate the ``password_retype`` field
Its value must match the ``password`` fields.
"""
if field.data != self.password.data: if field.data != self.password.data:
raise ValidationError(_('The two passwords must match!')) raise ValidationError(_('The two passwords must match!'))
class TimezoneField(SelectField): class TimezoneField(SelectField):
"""Field for selecting a time zone
Note: this field overrides whatever is passed to the ``choices`` parameter, and fills
``choices`` with the common timezones of pytz. In every other aspects, it behaves exactly
like `SelectField`.
"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.update({ kwargs.update({
'choices': [ 'choices': [
@ -44,6 +78,9 @@ class TimezoneField(SelectField):
@staticmethod @staticmethod
def is_pytz_instance(value): def is_pytz_instance(value):
"""Check if ``value`` is a valid pytz time zone instance
"""
return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo) return value is pytz.UTC or isinstance(value, pytz.tzinfo.BaseTzInfo)
def process_data(self, value): def process_data(self, value):
@ -68,6 +105,9 @@ class TimezoneField(SelectField):
class EventForm(FlaskForm): class EventForm(FlaskForm):
"""Form for event creation/editing
"""
title = StringField(_('Title'), validators=[DataRequired()]) title = StringField(_('Title'), validators=[DataRequired()])
time_zone = TimezoneField(_('Time zone'), validators=[DataRequired()]) time_zone = TimezoneField(_('Time zone'), validators=[DataRequired()])
start_time = DateTimeField(_('Start time'), validators=[DataRequired()]) start_time = DateTimeField(_('Start time'), validators=[DataRequired()])
@ -76,6 +116,9 @@ class EventForm(FlaskForm):
description = StringField(_('Description'), widget=TextArea()) description = StringField(_('Description'), widget=TextArea())
def populate_obj(self, obj): def populate_obj(self, obj):
"""Populate ``obj`` with event data
"""
FlaskForm.populate_obj(self, obj) FlaskForm.populate_obj(self, obj)
timezone = self.time_zone.data timezone = self.time_zone.data
@ -85,5 +128,10 @@ class EventForm(FlaskForm):
obj.end_time = timezone.localize(self.end_time.data).astimezone(pytz.utc) obj.end_time = timezone.localize(self.end_time.data).astimezone(pytz.utc)
def validate_end_time(self, field): def validate_end_time(self, field):
"""Validate the ``end_time`` field
Its value must be later than the value of the ``start_time`` field.
"""
if field.data < self.start_time.data: if field.data < self.start_time.data:
raise ValidationError(_('End time must be later than start time!')) raise ValidationError(_('End time must be later than start time!'))

View File

@ -1,3 +1,22 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Database models for Calendar.social
"""
from datetime import datetime from datetime import datetime
from flask_security import UserMixin, RoleMixin from flask_security import UserMixin, RoleMixin
@ -11,6 +30,9 @@ users_roles = db.Table(
class User(db.Model, UserMixin): class User(db.Model, UserMixin):
"""Database model for users
"""
__tablename__ = 'users' __tablename__ = 'users'
id = db.Column(db.Integer(), primary_key=True) id = db.Column(db.Integer(), primary_key=True)
@ -42,6 +64,9 @@ class User(db.Model, UserMixin):
class Role(db.Model, RoleMixin): class Role(db.Model, RoleMixin):
"""Database model for roles
"""
__tablename__ = 'roles' __tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True) id = db.Column(db.Integer(), primary_key=True)
@ -56,6 +81,9 @@ class Role(db.Model, RoleMixin):
class Event(db.Model): class Event(db.Model):
"""Database model for events
"""
__tablename__ = 'events' __tablename__ = 'events'
id = db.Column(db.Integer(), primary_key=True) id = db.Column(db.Integer(), primary_key=True)
@ -70,10 +98,10 @@ class Event(db.Model):
#: The time zone to be used for `start_time` and `end_time` #: The time zone to be used for `start_time` and `end_time`
time_zone = db.Column(db.String(length=80), nullable=False) time_zone = db.Column(db.String(length=80), nullable=False)
#: The starting timestamp of the event #: The starting timestamp of the event. It is in the UTC time zone
start_time = db.Column(db.DateTime(), nullable=False) start_time = db.Column(db.DateTime(), nullable=False)
#: The ending timestamp of the event #: The ending timestamp of the event. It is in the UTC time zone
end_time = db.Column(db.DateTime(), nullable=False) end_time = db.Column(db.DateTime(), nullable=False)
#: If `True`, the event is a whole-day event #: If `True`, the event is a whole-day event
@ -89,10 +117,16 @@ class Event(db.Model):
@property @property
def start_time_tz(self): def start_time_tz(self):
"""The same timestamp as `start_time`, but in the time zone specified by `time_zone`.
"""
return self.__as_tz(self.start_time) return self.__as_tz(self.start_time)
@property @property
def end_time_tz(self): def end_time_tz(self):
"""The same timestamp as `end_time`, but in the time zone specified by `time_zone`.
"""
return self.__as_tz(self.end_time) return self.__as_tz(self.end_time)
def __repr__(self): def __repr__(self):

View File

@ -1,3 +1,22 @@
# Calendar.social
# Copyright (C) 2018 Gergely Polonkai
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Security related things for Calendar.social
"""
from flask_security import Security from flask_security import Security
security = Security() security = Security()