[Refactor] Rename the app module to calsocial
Just for clarity
127
calsocial/__init__.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# 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/>.
|
||||
|
||||
from datetime import datetime
|
||||
import os
|
||||
|
||||
from flask import Flask, current_app, redirect, render_template, url_for
|
||||
from flask_babel import Babel, get_locale as babel_get_locale
|
||||
from flask_security import SQLAlchemyUserDatastore, current_user, login_required
|
||||
|
||||
|
||||
def get_locale():
|
||||
"""Locale selector
|
||||
|
||||
Selects the best locale based on values sent by the browser.
|
||||
"""
|
||||
|
||||
from flask import request
|
||||
|
||||
supported_languages = ['en', 'hu']
|
||||
|
||||
if 'l' in request.args and request.args['l'].lower() in supported_languages:
|
||||
return request.args['l'].lower()
|
||||
|
||||
return request.accept_languages.best_match(supported_languages)
|
||||
|
||||
|
||||
def template_vars():
|
||||
return {
|
||||
'lang': babel_get_locale().language,
|
||||
}
|
||||
|
||||
|
||||
class CalendarSocialApp(Flask):
|
||||
def __init__(self, name, config=None):
|
||||
from .models import db, User, Role
|
||||
from .security import security
|
||||
|
||||
Flask.__init__(self, name)
|
||||
|
||||
config_name = os.environ.get('ENV', config or 'dev')
|
||||
self.config.from_pyfile(f'config_{config_name}.py', True)
|
||||
# Make sure we look up users both by their usernames and email addresses
|
||||
self.config['SECURITY_USER_IDENTITY_ATTRIBUTES'] = ('username', 'email')
|
||||
db.init_app(self)
|
||||
babel = Babel(app=self)
|
||||
babel.localeselector(get_locale)
|
||||
|
||||
user_store = SQLAlchemyUserDatastore(db, User, Role)
|
||||
security.init_app(self, datastore=user_store)
|
||||
|
||||
self.context_processor(template_vars)
|
||||
|
||||
|
||||
app = CalendarSocialApp(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
from .calendar_system.gregorian import GregorianCalendar
|
||||
|
||||
if not current_user.is_authenticated:
|
||||
return render_template('welcome.html')
|
||||
|
||||
calendar = GregorianCalendar(datetime.utcnow().timestamp())
|
||||
|
||||
return render_template('index.html', calendar=calendar)
|
||||
|
||||
|
||||
@app.route('/register', methods=['POST', 'GET'])
|
||||
def register():
|
||||
if not current_app.config['REGISTRATION_ENABLED']:
|
||||
return render_template('registration-disabled.html')
|
||||
|
||||
from .forms import RegistrationForm
|
||||
from .models import db, User
|
||||
|
||||
form = RegistrationForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
# TODO: This might become False later, if we want registrations to be confirmed via E-mail
|
||||
user = User(active=True)
|
||||
|
||||
form.populate_obj(user)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('hello'))
|
||||
|
||||
return render_template('registration.html', form=form)
|
||||
|
||||
|
||||
@app.route('/new-event', methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def new_event():
|
||||
from .forms import EventForm
|
||||
from .models import db, Event
|
||||
|
||||
form = EventForm()
|
||||
|
||||
if form.validate_on_submit():
|
||||
event = Event(user=current_user)
|
||||
form.populate_obj(event)
|
||||
|
||||
db.session.add(event)
|
||||
db.session.commit()
|
||||
|
||||
return redirect(url_for('hello'))
|
||||
|
||||
return render_template('event-edit.html', form=form)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run()
|
15
calsocial/calendar_system/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
class CalendarSystem:
|
||||
__has_months__ = False
|
||||
__has_weeks__ = False
|
||||
|
||||
@property
|
||||
def day_names(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def month(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def days(self):
|
||||
raise NotImplementedError()
|
64
calsocial/calendar_system/gregorian.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from datetime import datetime, timedelta
|
||||
from flask_babel import lazy_gettext as _
|
||||
|
||||
from . import CalendarSystem
|
||||
|
||||
|
||||
class GregorianCalendar(CalendarSystem):
|
||||
__name__ = _('Gregorian')
|
||||
|
||||
__has_months__ = True
|
||||
|
||||
START_DAY = 0
|
||||
END_DAY = 7
|
||||
|
||||
day_names = (
|
||||
_('Monday'),
|
||||
_('Tuesday'),
|
||||
_('Wednesday'),
|
||||
_('Thursday'),
|
||||
_('Friday'),
|
||||
_('Saturday'),
|
||||
_('Sunday'),
|
||||
)
|
||||
|
||||
def __init__(self, timestamp):
|
||||
self.timestamp = datetime.utcfromtimestamp(timestamp)
|
||||
|
||||
@property
|
||||
def month(self):
|
||||
return self.timestamp.strftime("%B, %Y")
|
||||
|
||||
@property
|
||||
def days(self):
|
||||
day_list = []
|
||||
|
||||
start_day = self.timestamp.replace(day=1)
|
||||
|
||||
while start_day.weekday() > self.START_DAY:
|
||||
start_day -= timedelta(days=1)
|
||||
|
||||
day_list.append(start_day)
|
||||
current_day = start_day
|
||||
|
||||
while current_day.weekday() < self.END_DAY and current_day.month <= self.timestamp.month:
|
||||
current_day += timedelta(days=1)
|
||||
day_list.append(current_day)
|
||||
|
||||
return day_list
|
||||
|
||||
def day_events(self, date, user=None):
|
||||
from ..models import Event
|
||||
|
||||
events = Event.query
|
||||
|
||||
if user:
|
||||
events = events.filter(Event.user == user)
|
||||
|
||||
start_timestamp = date.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
end_timestamp = start_timestamp + timedelta(days=1)
|
||||
|
||||
events = events.filter(((Event.start_time >= start_timestamp) & (Event.start_time < end_timestamp)) |
|
||||
((Event.end_time >= start_timestamp) & (Event.end_time < end_timestamp)))
|
||||
|
||||
return events
|
10
calsocial/config_dev.py
Normal file
@@ -0,0 +1,10 @@
|
||||
DEBUG = True
|
||||
ENV = 'dev'
|
||||
REGISTRATION_ENABLED = True
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = 'sqlite:///local.db'
|
||||
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
||||
SECRET_KEY = 'ThisIsNotSoSecret'
|
||||
SECURITY_PASSWORD_HASH = 'bcrypt'
|
||||
SECURITY_PASSWORD_SALT = SECRET_KEY
|
||||
SECURITY_REGISTERABLE = False
|
30
calsocial/forms.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from flask_babel import lazy_gettext as _
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import PasswordField, StringField, BooleanField
|
||||
from wtforms.ext.dateutil.fields import DateTimeField
|
||||
from wtforms.validators import DataRequired, Email, ValidationError
|
||||
from wtforms.widgets import TextArea
|
||||
|
||||
|
||||
class RegistrationForm(FlaskForm):
|
||||
username = StringField(_('Username'), validators=[DataRequired()])
|
||||
email = StringField(_('Email address'), validators=[Email()])
|
||||
password = PasswordField(_('Password'), validators=[DataRequired()])
|
||||
password_retype = PasswordField(_('Password, once more'), validators=[DataRequired()])
|
||||
|
||||
def validate_password_retype(self, field):
|
||||
if field.data != self.password.data:
|
||||
raise ValidationError(_('The two passwords must match!'))
|
||||
|
||||
|
||||
class EventForm(FlaskForm):
|
||||
title = StringField(_('Title'), validators=[DataRequired()])
|
||||
time_zone = StringField(_('Time zone'), validators=[DataRequired()])
|
||||
start_time = DateTimeField(_('Start time'), validators=[DataRequired()])
|
||||
end_time = DateTimeField(_('End time'), validators=[DataRequired()])
|
||||
all_day = BooleanField(_('All day'))
|
||||
description = StringField(_('Description'), widget=TextArea())
|
||||
|
||||
def validate_end_time(self, field):
|
||||
if field.data < self.start_time.data:
|
||||
raise ValidationError(_('End time must be later than start time!'))
|
87
calsocial/models.py
Normal file
@@ -0,0 +1,87 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask_security import UserMixin, RoleMixin
|
||||
from flask_security.utils import hash_password
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
users_roles = db.Table(
|
||||
'users_roles',
|
||||
db.Column('user_id', db.Integer(), db.ForeignKey('users.id')),
|
||||
db.Column('role_id', db.Integer(), db.ForeignKey('roles.id')))
|
||||
|
||||
|
||||
class User(db.Model, UserMixin):
|
||||
__tablename__ = 'users'
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
|
||||
#: The username of the user. This is also the display name and thus is immutable
|
||||
username = db.Column(db.String(length=50), unique=True, nullable=False)
|
||||
|
||||
#: The email address of the user
|
||||
email = db.Column(db.String(length=255), unique=True, nullable=True)
|
||||
|
||||
#: The (hashed) password of the user
|
||||
password = db.Column(db.String(length=255))
|
||||
|
||||
#: A flag to show whether the user is enabled (active) or not
|
||||
active = db.Column(db.Boolean(), default=False)
|
||||
|
||||
#: The timestamp when this user was created
|
||||
created_at = db.Column(db.DateTime(), default=datetime.utcnow)
|
||||
|
||||
#: The timestamp when the user was activated
|
||||
confirmed_at = db.Column(db.DateTime())
|
||||
|
||||
#: The roles of the user
|
||||
roles = db.relationship('Role',
|
||||
secondary=users_roles,
|
||||
backref=db.backref('users', lazy='dynamic'))
|
||||
|
||||
def __repr__(self):
|
||||
return f'<User {self.id}({self.username})>'
|
||||
|
||||
|
||||
class Role(db.Model, RoleMixin):
|
||||
__tablename__ = 'roles'
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
|
||||
#: The name of the role
|
||||
name = db.Column(db.Unicode(length=80), unique=True)
|
||||
|
||||
#: A description of the role
|
||||
description = db.Column(db.UnicodeText)
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Role {self.id}({self.name})>'
|
||||
|
||||
|
||||
class Event(db.Model):
|
||||
__tablename__ = 'events'
|
||||
id = db.Column(db.Integer(), primary_key=True)
|
||||
|
||||
#: The ID of the user who created the event
|
||||
user_id = db.Column(db.Integer(), db.ForeignKey('users.id'), nullable=False)
|
||||
|
||||
user = db.relationship('User', backref=db.backref('events', lazy='dynamic'))
|
||||
|
||||
#: The title of the event
|
||||
title = db.Column(db.Unicode(length=200), nullable=False)
|
||||
|
||||
#: The time zone to be used for `start_time` and `end_time`
|
||||
time_zone = db.Column(db.String(length=80), nullable=False)
|
||||
|
||||
#: The starting timestamp of the event
|
||||
start_time = db.Column(db.DateTime(), nullable=False)
|
||||
|
||||
#: The ending timestamp of the event
|
||||
end_time = db.Column(db.DateTime(), nullable=False)
|
||||
|
||||
#: If `True`, the event is a whole-day event
|
||||
all_day = db.Column(db.Boolean(), default=False)
|
||||
|
||||
#: The description of the event
|
||||
description = db.Column(db.UnicodeText())
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Event {self.id} ({self.title}) of {self.user}>'
|
3
calsocial/security.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from flask_security import Security
|
||||
|
||||
security = Security()
|
BIN
calsocial/static/images/calendar-social-icon-16.png
Normal file
After Width: | Height: | Size: 653 B |
BIN
calsocial/static/images/calendar-social-icon-192.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
calsocial/static/images/calendar-social-icon-32.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
calsocial/static/images/calendar-social-icon-400.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
calsocial/static/images/calendar-social-icon-96.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
201
calsocial/static/images/calendar-social-icon-no-circle.svg
Normal file
@@ -0,0 +1,201 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 46 46"
|
||||
xml:space="preserve"
|
||||
id="svg4656"
|
||||
sodipodi:docname="calendar-social-icon-no-circle.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
inkscape:export-filename="/home/polesz/Pictures/calendar-social-icon-32.png"
|
||||
inkscape:export-xdpi="66.639999"
|
||||
inkscape:export-ydpi="66.639999"
|
||||
width="46"
|
||||
height="46"><metadata
|
||||
id="metadata4662"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs4660" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="997"
|
||||
id="namedview4658"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="12.121291"
|
||||
inkscape:cy="12.202432"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="3"
|
||||
fit-margin-right="3"
|
||||
fit-margin-bottom="0" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style4610">
|
||||
.st0{fill:#77B3D4;}
|
||||
.st1{opacity:0.2;}
|
||||
.st2{fill:#231F20;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
.st4{fill:#C75C5C;}
|
||||
.st5{fill:#4F5D73;}
|
||||
.st6{fill:#E0E0D1;}
|
||||
</style>
|
||||
<g
|
||||
id="Layer_1"
|
||||
transform="translate(-9,-8)">
|
||||
<g
|
||||
id="g4614">
|
||||
|
||||
</g>
|
||||
<g
|
||||
id="g4652">
|
||||
<g
|
||||
class="st1"
|
||||
id="g4618"
|
||||
style="opacity:0.2">
|
||||
<path
|
||||
class="st2"
|
||||
d="m 12,25 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 25 Z"
|
||||
id="path4616"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#231f20" />
|
||||
</g>
|
||||
<g
|
||||
id="g4622">
|
||||
<path
|
||||
class="st3"
|
||||
d="m 12,23 v 25 c 0,2.2 1.8,4 4,4 h 32 c 2.2,0 4,-1.8 4,-4 V 23 Z"
|
||||
id="path4620"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff" />
|
||||
</g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4626"
|
||||
style="opacity:0.2">
|
||||
<path
|
||||
class="st2"
|
||||
d="M 48,14 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
|
||||
id="path4624"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#231f20" />
|
||||
</g>
|
||||
<g
|
||||
id="g4630">
|
||||
<path
|
||||
class="st4"
|
||||
d="M 48,12 H 16 c -2.2,0 -4,1.8 -4,4 v 7 h 40 v -7 c 0,-2.2 -1.8,-4 -4,-4 z"
|
||||
id="path4628"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#c75c5c" />
|
||||
</g>
|
||||
<g
|
||||
id="g4634-3"
|
||||
transform="matrix(0.57125605,0,0,0.57125605,20.265475,18.937918)"><path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632-6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#c75c5c;fill-opacity:1" /><circle
|
||||
style="fill:#c75c5c;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664-7"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828" /></g><g
|
||||
id="g4634-3-6"
|
||||
transform="matrix(0.57125605,0,0,0.57125605,7.0614955,19.226475)"><path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632-6-2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#77b3d4;fill-opacity:1"
|
||||
inkscape:export-xdpi="600"
|
||||
inkscape:export-ydpi="600" /><circle
|
||||
style="fill:#77b3d4;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664-7-9"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828"
|
||||
inkscape:export-xdpi="600"
|
||||
inkscape:export-ydpi="600" /></g><g
|
||||
id="g4634"
|
||||
transform="matrix(0.7302377,0,0,0.7302377,8.769411,12.94859)">
|
||||
<path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#4f5d73" />
|
||||
<circle
|
||||
style="fill:#4f5d73;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828" /></g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4638"
|
||||
style="opacity:0.2">
|
||||
<path
|
||||
class="st2"
|
||||
d="m 20,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4636"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#231f20" />
|
||||
</g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4642"
|
||||
style="opacity:0.2">
|
||||
<path
|
||||
class="st2"
|
||||
d="m 45,21 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4640"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#231f20" />
|
||||
</g>
|
||||
<g
|
||||
id="g4646">
|
||||
<path
|
||||
class="st6"
|
||||
d="m 20,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4644"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#e0e0d1" />
|
||||
</g>
|
||||
<g
|
||||
id="g4650">
|
||||
<path
|
||||
class="st6"
|
||||
d="m 45,19 c -1.1,0 -2,-0.9 -2,-2 v -7 c 0,-1.1 0.9,-2 2,-2 v 0 c 1.1,0 2,0.9 2,2 v 7 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4648"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#e0e0d1" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="Layer_2"
|
||||
transform="translate(-9,-8)">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
179
calsocial/static/images/calendar-social-icon.svg
Normal file
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 64 64"
|
||||
xml:space="preserve"
|
||||
id="svg4656"
|
||||
sodipodi:docname="calendar-social-icon.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
inkscape:export-filename="/home/polesz/Pictures/calendar-social-icon-192.png"
|
||||
inkscape:export-xdpi="288"
|
||||
inkscape:export-ydpi="288"
|
||||
style="enable-background:new 0 0 64 64;"><metadata
|
||||
id="metadata4662"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs4660" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="997"
|
||||
id="namedview4658"
|
||||
showgrid="false"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="-38.878709"
|
||||
inkscape:cy="11.202432"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="23"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<style
|
||||
type="text/css"
|
||||
id="style4610">
|
||||
.st0{fill:#77B3D4;}
|
||||
.st1{opacity:0.2;}
|
||||
.st2{fill:#231F20;}
|
||||
.st3{fill:#FFFFFF;}
|
||||
.st4{fill:#C75C5C;}
|
||||
.st5{fill:#4F5D73;}
|
||||
.st6{fill:#E0E0D1;}
|
||||
</style>
|
||||
<g
|
||||
id="Layer_1">
|
||||
<g
|
||||
id="g4614">
|
||||
<circle
|
||||
class="st0"
|
||||
cx="32"
|
||||
cy="32"
|
||||
r="32"
|
||||
id="circle4612" />
|
||||
</g>
|
||||
<g
|
||||
id="g4652">
|
||||
<g
|
||||
class="st1"
|
||||
id="g4618">
|
||||
<path
|
||||
class="st2"
|
||||
d="M12,25v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V25H12z"
|
||||
id="path4616" />
|
||||
</g>
|
||||
<g
|
||||
id="g4622">
|
||||
<path
|
||||
class="st3"
|
||||
d="M12,23v25c0,2.2,1.8,4,4,4h32c2.2,0,4-1.8,4-4V23H12z"
|
||||
id="path4620" />
|
||||
</g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4626">
|
||||
<path
|
||||
class="st2"
|
||||
d="M48,14H16c-2.2,0-4,1.8-4,4v7h40v-7C52,15.8,50.2,14,48,14z"
|
||||
id="path4624" />
|
||||
</g>
|
||||
<g
|
||||
id="g4630">
|
||||
<path
|
||||
class="st4"
|
||||
d="M48,12H16c-2.2,0-4,1.8-4,4v7h40v-7C52,13.8,50.2,12,48,12z"
|
||||
id="path4628" />
|
||||
</g>
|
||||
<g
|
||||
id="g4634-3"
|
||||
transform="matrix(0.57125605,0,0,0.57125605,20.265475,18.937918)"><path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632-6"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#c75c5c;fill-opacity:1" /><circle
|
||||
style="fill:#c75c5c;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664-7"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828" /></g><g
|
||||
id="g4634-3-6"
|
||||
transform="matrix(0.57125605,0,0,0.57125605,7.0614955,19.226475)"><path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632-6-2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#77b3d4;fill-opacity:1"
|
||||
inkscape:export-xdpi="600"
|
||||
inkscape:export-ydpi="600" /><circle
|
||||
style="fill:#77b3d4;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664-7-9"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828"
|
||||
inkscape:export-xdpi="600"
|
||||
inkscape:export-ydpi="600" /></g><g
|
||||
id="g4634"
|
||||
transform="matrix(0.7302377,0,0,0.7302377,8.769411,12.94859)">
|
||||
<path
|
||||
class="st5"
|
||||
d="m 32,48 c -1.1,0 -2,-0.9 -2,-2 0,-5.5 1.8,-9.5 3.5,-12 H 27 c -1.1,0 -2,-0.9 -2,-2 0,-1.1 0.9,-2 2,-2 h 11 c 0.9,0 1.6,0.6 1.9,1.4 0.3,0.8 0,1.7 -0.7,2.2 C 39,33.8 34,37.5 34,46 c 0,1.1 -0.9,2 -2,2 z"
|
||||
id="path4632"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#4f5d73" />
|
||||
<circle
|
||||
style="fill:#4f5d73;fill-opacity:1;stroke:none;stroke-width:9.57298088;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
id="path4664"
|
||||
cx="32.684433"
|
||||
cy="23.473127"
|
||||
r="5.0143828" /></g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4638">
|
||||
<path
|
||||
class="st2"
|
||||
d="M20,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,20.1,21.1,21,20,21L20,21z"
|
||||
id="path4636" />
|
||||
</g>
|
||||
<g
|
||||
class="st1"
|
||||
id="g4642">
|
||||
<path
|
||||
class="st2"
|
||||
d="M45,21c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,20.1,46.1,21,45,21L45,21z"
|
||||
id="path4640" />
|
||||
</g>
|
||||
<g
|
||||
id="g4646">
|
||||
<path
|
||||
class="st6"
|
||||
d="M20,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C22,18.1,21.1,19,20,19L20,19z"
|
||||
id="path4644" />
|
||||
</g>
|
||||
<g
|
||||
id="g4650">
|
||||
<path
|
||||
class="st6"
|
||||
d="M45,19c-1.1,0-2-0.9-2-2v-7c0-1.1,0.9-2,2-2l0,0c1.1,0,2,0.9,2,2v7C47,18.1,46.1,19,45,19L45,19z"
|
||||
id="path4648" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="Layer_2">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 5.5 KiB |
51
calsocial/templates/base.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Calendar.Social</title>
|
||||
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='images/calendar-social-icon-16.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/calendar-social-icon-32.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/calendar-social-icon-96.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/calendar-social-icon-192.png') }}">
|
||||
{% block head %}
|
||||
<style>
|
||||
header > h1 > img {
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 3em;
|
||||
font-weight: bold;
|
||||
border-top: 1px dotted black;
|
||||
padding-top: 1em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>
|
||||
<img src="{{ url_for('static', filename='images/calendar-social-icon.svg') }}">
|
||||
Calendar.social
|
||||
</h1>
|
||||
<nav class="menu">
|
||||
{% if current_user.is_authenticated %}
|
||||
{{ _('Logged in as %(username)s', username=current_user.username) }}
|
||||
{% endif %}
|
||||
<ul>
|
||||
{% if not current_user.is_authenticated %}
|
||||
<li><a href="{{ url_for('security.login') }}">{% trans %}Login{% endtrans %}</a></li>
|
||||
{% else %}
|
||||
<li><a href="{{ url_for('security.logout') }}">{% trans %}Logout{% endtrans %}</a></li>
|
||||
<li><a href="{{ url_for('hello') }}">{% trans %}Calendar view{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</header>
|
||||
{% block content %}{% endblock %}
|
||||
<footer>
|
||||
Soon…™
|
||||
</footer>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
43
calsocial/templates/event-edit.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ form.errors }}
|
||||
<br>
|
||||
|
||||
{{ form.title.errors }}
|
||||
{{ form.title.label }}
|
||||
{{ form.title }}
|
||||
<br>
|
||||
|
||||
{{ form.time_zone.errors }}
|
||||
{{ form.time_zone.label }}
|
||||
{{ form.time_zone }}
|
||||
<br>
|
||||
|
||||
{{ form.start_time.errors }}
|
||||
{{ form.start_time.label }}
|
||||
{{ form.start_time }}
|
||||
<br>
|
||||
|
||||
{{ form.end_time.errors }}
|
||||
{{ form.end_time.label }}
|
||||
{{ form.end_time }}
|
||||
<br>
|
||||
|
||||
{{ form.all_day.errors }}
|
||||
{{ form.all_day.label }}
|
||||
{{ form.all_day }}
|
||||
<br>
|
||||
|
||||
{{ form.description.errors }}
|
||||
{{ form.description.label }}
|
||||
{{ form.description }}
|
||||
<br>
|
||||
|
||||
<button type="submit">{% trans %}Save{% endtrans %}</button>
|
||||
<a href="{{ url_for('hello') }}">Cancel</a>
|
||||
</form>
|
||||
{% endblock content %}
|
9
calsocial/templates/index.html
Normal file
@@ -0,0 +1,9 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{{ _('Welcome to Calendar.social, %(username)s!', username=current_user.username) }}
|
||||
|
||||
{% include 'month-view.html' %}
|
||||
|
||||
<a href="{{ url_for('new_event') }}">{% trans %}Add event{% endtrans %}</a>
|
||||
{% endblock content %}
|
82
calsocial/templates/month-view.html
Normal file
@@ -0,0 +1,82 @@
|
||||
<style>
|
||||
table.calendar > * {
|
||||
font-family: sans;
|
||||
}
|
||||
|
||||
table.calendar {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
tr.month > td {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid black;
|
||||
padding-bottom: .5em;
|
||||
}
|
||||
|
||||
tr.days > td {
|
||||
text-align: center;
|
||||
border-bottom: 2px solid black;
|
||||
}
|
||||
|
||||
tr.week > td {
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
tr.week > td > span.day-num {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr.week > td.other-month > span.day-num {
|
||||
font-weight: normal;
|
||||
color: #909090;
|
||||
}
|
||||
|
||||
tr.week > td.today {
|
||||
background-color: #d8d8d8;
|
||||
}
|
||||
|
||||
td > div.event {
|
||||
border: 1px solid green;
|
||||
background-color: white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
<table class="calendar">
|
||||
<thead>
|
||||
<tr class="month">
|
||||
<td colspan="7">{{ calendar.month }}</td>
|
||||
</tr>
|
||||
<tr class="days">
|
||||
{% for day in calendar.day_names %}
|
||||
<td>
|
||||
{{ day }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% set markers = namespace() -%}
|
||||
{% set markers.first = true -%}
|
||||
{% for day in calendar.days -%}
|
||||
{% if loop.index0 % (calendar.day_names) | length == 0 -%}
|
||||
{% if not markers.first %}
|
||||
</tr>
|
||||
{%- else %}
|
||||
{%- set markers.first = false %}
|
||||
{%- endif %}
|
||||
<tr class="week">
|
||||
{%- endif %}
|
||||
<td class="{% if day.month != calendar.timestamp.month %} other-month{% endif %}{% if day.date() == calendar.timestamp.date() %} today{% endif %}">
|
||||
<span class="day-num">{{ day.day }}</span>
|
||||
{% for event in calendar.day_events(day, user=current_user) %}
|
||||
<div class="event">
|
||||
{{ event.title }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
5
calsocial/templates/registration-disabled.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Registration is disabled on this instance</h1>
|
||||
{% endblock content %}
|
29
calsocial/templates/registration.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<form method="post">
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{{ form.username.errors }}
|
||||
{{ form.username.label }}
|
||||
{{ form.username }}
|
||||
<br>
|
||||
|
||||
{{ form.email.errors }}
|
||||
{{ form.email.label }}
|
||||
{{ form.email }}
|
||||
<br>
|
||||
|
||||
{{ form.password.errors }}
|
||||
{{ form.password.label }}
|
||||
{{ form.password }}
|
||||
<br>
|
||||
|
||||
{{ form.password_retype.errors }}
|
||||
{{ form.password_retype.label }}
|
||||
{{ form.password_retype }}
|
||||
<br>
|
||||
|
||||
<button type="submit">{% trans %}Register{% endtrans %}</button>
|
||||
</form>
|
||||
{% endblock %}
|
5
calsocial/templates/welcome.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<p>Welcome to Calendar.social. There will be lot of content here soon!</p>
|
||||
{% endblock content %}
|
61
calsocial/translations/hu/LC_MESSAGES/messages.po
Normal file
@@ -0,0 +1,61 @@
|
||||
# Hungarian translations for PROJECT.
|
||||
# Copyright (C) 2018 ORGANIZATION
|
||||
# This file is distributed under the same license as the PROJECT project.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1\n"
|
||||
"Report-Msgid-Bugs-To: gergely@polonkai.eu\n"
|
||||
"POT-Creation-Date: 2018-06-29 14:14+0200\n"
|
||||
"PO-Revision-Date: 2018-06-29 14:26+0200\n"
|
||||
"Last-Translator: Gergely Polonkai <gergely@polonkai.eu>\n"
|
||||
"Language: hu\n"
|
||||
"Language-Team: hu <gergely@polonkai.eu>\n"
|
||||
"Plural-Forms: nplurals=1; plural=0\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Generated-By: Babel 2.6.0\n"
|
||||
|
||||
#: app/forms.py:8
|
||||
msgid "Username"
|
||||
msgstr "Felhasználónév"
|
||||
|
||||
#: app/forms.py:9
|
||||
msgid "Email address"
|
||||
msgstr "E-mail cím"
|
||||
|
||||
#: app/forms.py:10
|
||||
msgid "Password"
|
||||
msgstr "Jelszó"
|
||||
|
||||
#: app/forms.py:11
|
||||
msgid "Password, once more"
|
||||
msgstr "Jelszó még egszer"
|
||||
|
||||
#: app/forms.py:15
|
||||
msgid "The two passwords must match!"
|
||||
msgstr "A két jelszónak egyeznie kell!"
|
||||
|
||||
#: app/templates/base.html:21
|
||||
#, python-format
|
||||
msgid "Logged in as %(username)s"
|
||||
msgstr "Bejelentkezve %(username)s néven"
|
||||
|
||||
#: app/templates/base.html:25
|
||||
msgid "Login"
|
||||
msgstr "Bejelentkezés"
|
||||
|
||||
#: app/templates/base.html:27
|
||||
msgid "Logout"
|
||||
msgstr "Kijelentkezés"
|
||||
|
||||
#: app/templates/index.html:4
|
||||
#, python-format
|
||||
msgid "Welcome to Calendar.social, %(username)s!"
|
||||
msgstr "Üdv a Calendar.social-ben, %(username)s!"
|
||||
|
||||
#: app/templates/registration.html:27
|
||||
msgid "Register"
|
||||
msgstr "Regisztráció"
|