Format core

This commit is contained in:
Pierre Brunet 2017-06-12 09:42:03 +02:00
parent 4f4ea5dde9
commit e7de8b2aec
20 changed files with 515 additions and 415 deletions

View File

@ -33,8 +33,9 @@ admin.site.unregister(AuthGroup)
admin.site.register(RealGroup) admin.site.register(RealGroup)
admin.site.register(Page) admin.site.register(Page)
@admin.register(SithFile) @admin.register(SithFile)
class SithFileAdmin(admin.ModelAdmin): class SithFileAdmin(admin.ModelAdmin):
form = make_ajax_form(SithFile, { form = make_ajax_form(SithFile, {
'parent': 'files', # ManyToManyField 'parent': 'files', # ManyToManyField
}) })

View File

@ -23,9 +23,9 @@
# #
from django.apps import AppConfig from django.apps import AppConfig
from django.dispatch import receiver
from django.core.signals import request_started from django.core.signals import request_started
class SithConfig(AppConfig): class SithConfig(AppConfig):
name = 'core' name = 'core'
verbose_name = "Core app of the Sith" verbose_name = "Core app of the Sith"
@ -48,4 +48,3 @@ class SithConfig(AppConfig):
request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups") request_started.connect(clear_cached_groups, weak=False, dispatch_uid="clear_cached_groups")
request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships") request_started.connect(clear_cached_memberships, weak=False, dispatch_uid="clear_cached_memberships")
# TODO: there may be a need to add more cache clearing # TODO: there may be a need to add more cache clearing

View File

@ -31,16 +31,19 @@ from club.models import Club
from counter.models import Product, Counter from counter.models import Product, Counter
from accounting.models import ClubAccount, Company from accounting.models import ClubAccount, Company
def check_token(request): def check_token(request):
return ('counter_token' in request.session.keys() and return ('counter_token' in request.session.keys() and
request.session['counter_token'] and request.session['counter_token'] and
Counter.objects.filter(token=request.session['counter_token']).exists()) Counter.objects.filter(token=request.session['counter_token']).exists())
class RightManagedLookupChannel(LookupChannel): class RightManagedLookupChannel(LookupChannel):
def check_auth(self, request): def check_auth(self, request):
if not request.user.was_subscribed and not check_token(request): if not request.user.was_subscribed and not check_token(request):
raise PermissionDenied raise PermissionDenied
@register('users') @register('users')
class UsersLookup(RightManagedLookupChannel): class UsersLookup(RightManagedLookupChannel):
model = User model = User
@ -54,6 +57,7 @@ class UsersLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.get_display_name() return item.get_display_name()
@register('groups') @register('groups')
class GroupsLookup(RightManagedLookupChannel): class GroupsLookup(RightManagedLookupChannel):
model = Group model = Group
@ -67,6 +71,7 @@ class GroupsLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('clubs') @register('clubs')
class ClubLookup(RightManagedLookupChannel): class ClubLookup(RightManagedLookupChannel):
model = Club model = Club
@ -80,6 +85,7 @@ class ClubLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('counters') @register('counters')
class CountersLookup(RightManagedLookupChannel): class CountersLookup(RightManagedLookupChannel):
model = Counter model = Counter
@ -90,6 +96,7 @@ class CountersLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('products') @register('products')
class ProductsLookup(RightManagedLookupChannel): class ProductsLookup(RightManagedLookupChannel):
model = Product model = Product
@ -101,6 +108,7 @@ class ProductsLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return "%s (%s)" % (item.name, item.code) return "%s (%s)" % (item.name, item.code)
@register('files') @register('files')
class SithFileLookup(RightManagedLookupChannel): class SithFileLookup(RightManagedLookupChannel):
model = SithFile model = SithFile
@ -108,6 +116,7 @@ class SithFileLookup(RightManagedLookupChannel):
def get_query(self, q, request): def get_query(self, q, request):
return self.model.objects.filter(name__icontains=q)[:50] return self.model.objects.filter(name__icontains=q)[:50]
@register('club_accounts') @register('club_accounts')
class ClubAccountLookup(RightManagedLookupChannel): class ClubAccountLookup(RightManagedLookupChannel):
model = ClubAccount model = ClubAccount
@ -118,6 +127,7 @@ class ClubAccountLookup(RightManagedLookupChannel):
def format_item_display(self, item): def format_item_display(self, item):
return item.name return item.name
@register('companies') @register('companies')
class CompaniesLookup(RightManagedLookupChannel): class CompaniesLookup(RightManagedLookupChannel):
model = Company model = Company

View File

@ -21,4 +21,3 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #

View File

@ -44,7 +44,6 @@ class Command(BaseCommand):
args['precision'] = settings.SASS_PRECISION args['precision'] = settings.SASS_PRECISION
return sass.compile(**args) return sass.compile(**args)
def is_compilable(self, file, ext_list): def is_compilable(self, file, ext_list):
path, ext = os.path.splitext(file) path, ext = os.path.splitext(file)
return ext in ext_list return ext in ext_list

View File

@ -26,7 +26,7 @@ import os
from datetime import date, datetime from datetime import date, datetime
from io import StringIO, BytesIO from io import StringIO, BytesIO
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings from django.conf import settings
from django.db import connection from django.db import connection
@ -42,7 +42,7 @@ from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter from counter.models import Customer, ProductType, Product, Counter
from com.models import Sith, Weekmail from com.models import Sith, Weekmail
from election.models import Election, Role, Candidature, ElectionList from election.models import Election, Role, Candidature, ElectionList
from forum.models import Forum, ForumMessage, ForumTopic from forum.models import Forum, ForumTopic
class Command(BaseCommand): class Command(BaseCommand):
@ -75,9 +75,9 @@ class Command(BaseCommand):
Group(name="Forum admin").save() Group(name="Forum admin").save()
self.reset_index("core", "auth") self.reset_index("core", "auth")
root = User(id=0, username='root', last_name="", first_name="Bibou", root = User(id=0, username='root', last_name="", first_name="Bibou",
email="ae.info@utbm.fr", email="ae.info@utbm.fr",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=True, is_staff=True) is_superuser=True, is_staff=True)
root.set_password("plop") root.set_password("plop")
root.save() root.save()
profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root) profiles_root = SithFile(parent=None, name="profiles", is_folder=True, owner=root)
@ -88,18 +88,18 @@ class Command(BaseCommand):
club_root.save() club_root.save()
SithFile(parent=None, name="SAS", is_folder=True, owner=root).save() SithFile(parent=None, name="SAS", is_folder=True, owner=root).save()
main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'], main_club = Club(id=1, name=settings.SITH_MAIN_CLUB['name'], unix_name=settings.SITH_MAIN_CLUB['unix_name'],
address=settings.SITH_MAIN_CLUB['address']) address=settings.SITH_MAIN_CLUB['address'])
main_club.save() main_club.save()
bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'], bar_club = Club(id=2, name=settings.SITH_BAR_MANAGER['name'], unix_name=settings.SITH_BAR_MANAGER['unix_name'],
address=settings.SITH_BAR_MANAGER['address']) address=settings.SITH_BAR_MANAGER['address'])
bar_club.save() bar_club.save()
launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'], launderette_club = Club(id=84, name=settings.SITH_LAUNDERETTE_MANAGER['name'],
unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'], unix_name=settings.SITH_LAUNDERETTE_MANAGER['unix_name'],
address=settings.SITH_LAUNDERETTE_MANAGER['address']) address=settings.SITH_LAUNDERETTE_MANAGER['address'])
launderette_club.save() launderette_club.save()
self.reset_index("club") self.reset_index("club")
for b in settings.SITH_COUNTER_BARS: for b in settings.SITH_COUNTER_BARS:
g = Group(name=b[1]+" admin") g = Group(name=b[1] + " admin")
g.save() g.save()
c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR') c = Counter(id=b[0], name=b[1], club=bar_club, type='BAR')
c.save() c.save()
@ -120,7 +120,7 @@ class Command(BaseCommand):
p = Page(name='Index') p = Page(name='Index')
p.set_lock(root) p.set_lock(root)
p.save() p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root) p.set_lock(root)
p.save() p.save()
PageRev(page=p, title="Wiki index", author=root, content=""" PageRev(page=p, title="Wiki index", author=root, content="""
@ -130,7 +130,7 @@ Welcome to the wiki page!
p = Page(name="services") p = Page(name="services")
p.set_lock(root) p.set_lock(root)
p.save() p.save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.set_lock(root) p.set_lock(root)
PageRev(page=p, title="Services", author=root, content=""" PageRev(page=p, title="Services", author=root, content="""
| | | | | | | |
@ -150,18 +150,18 @@ Welcome to the wiki page!
if not options['prod']: if not options['prod']:
# Adding user Skia # Adding user Skia
skia = User(username='skia', last_name="Kia", first_name="S'", skia = User(username='skia', last_name="Kia", first_name="S'",
email="skia@git.an", email="skia@git.an",
date_of_birth="1942-06-12") date_of_birth="1942-06-12")
skia.set_password("plop") skia.set_password("plop")
skia.save() skia.save()
skia.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] skia.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
skia.save() skia.save()
skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg') skia_profile_path = os.path.join(root_path, 'core/fixtures/images/3.jpg')
with open(skia_profile_path, 'rb') as f: with open(skia_profile_path, 'rb') as f:
name = str(skia.id) + "_profile.jpg" name = str(skia.id) + "_profile.jpg"
skia_profile = SithFile(parent=profiles_root, name=name, skia_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path)) owner=skia, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(skia_profile_path))
skia_profile.file.name = name skia_profile.file.name = name
skia_profile.save() skia_profile.save()
skia.profile_pict = skia_profile skia.profile_pict = skia_profile
@ -169,50 +169,50 @@ Welcome to the wiki page!
# Adding user public # Adding user public
public = User(username='public', last_name="Not subscribed", first_name="Public", public = User(username='public', last_name="Not subscribed", first_name="Public",
email="public@git.an", email="public@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
public.set_password("plop") public.set_password("plop")
public.save() public.save()
public.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] public.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
public.save() public.save()
# Adding user Subscriber # Adding user Subscriber
subscriber = User(username='subscriber', last_name="User", first_name="Subscribed", subscriber = User(username='subscriber', last_name="User", first_name="Subscribed",
email="Subscribed@git.an", email="Subscribed@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
subscriber.set_password("plop") subscriber.set_password("plop")
subscriber.save() subscriber.save()
subscriber.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
subscriber.save() subscriber.save()
# Adding user old Subscriber # Adding user old Subscriber
old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old", old_subscriber = User(username='old_subscriber', last_name="Subscriber", first_name="Old",
email="old_subscriber@git.an", email="old_subscriber@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
old_subscriber.set_password("plop") old_subscriber.set_password("plop")
old_subscriber.save() old_subscriber.save()
old_subscriber.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] old_subscriber.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
old_subscriber.save() old_subscriber.save()
# Adding user Counter admin # Adding user Counter admin
counter = User(username='counter', last_name="Ter", first_name="Coun", counter = User(username='counter', last_name="Ter", first_name="Coun",
email="counter@git.an", email="counter@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
counter.set_password("plop") counter.set_password("plop")
counter.save() counter.save()
counter.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] counter.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
counter.groups=[Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id] counter.groups = [Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id]
counter.save() counter.save()
# Adding user Comptable # Adding user Comptable
comptable = User(username='comptable', last_name="Able", first_name="Compte", comptable = User(username='comptable', last_name="Able", first_name="Compte",
email="compta@git.an", email="compta@git.an",
date_of_birth="1942-06-12", date_of_birth="1942-06-12",
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
comptable.set_password("plop") comptable.set_password("plop")
comptable.save() comptable.save()
comptable.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] comptable.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
comptable.groups=[Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id] comptable.groups = [Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID).first().id]
comptable.save() comptable.save()
# Adding user Guy # Adding user Guy
u = User(username='guy', last_name="Carlier", first_name="Guy", u = User(username='guy', last_name="Carlier", first_name="Guy",
@ -221,7 +221,7 @@ Welcome to the wiki page!
is_superuser=False, is_staff=False) is_superuser=False, is_staff=False)
u.set_password("plop") u.set_password("plop")
u.save() u.save()
u.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] u.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
u.save() u.save()
# Adding user Richard Batsbak # Adding user Richard Batsbak
r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard", r = User(username='rbatsbak', last_name="Batsbak", first_name="Richard",
@ -229,18 +229,18 @@ Welcome to the wiki page!
date_of_birth="1982-06-12") date_of_birth="1982-06-12")
r.set_password("plop") r.set_password("plop")
r.save() r.save()
r.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] r.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
r.save() r.save()
# Adding syntax help page # Adding syntax help page
p = Page(name='Aide_sur_la_syntaxe') p = Page(name='Aide_sur_la_syntaxe')
p.save(force_lock=True) p.save(force_lock=True)
with open(os.path.join(root_path)+'/doc/SYNTAX.md', 'r') as rm: with open(os.path.join(root_path) + '/doc/SYNTAX.md', 'r') as rm:
PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save() PageRev(page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()).save()
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
p = Page(name='Services') p = Page(name='Services')
p.save(force_lock=True) p.save(force_lock=True)
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
PageRev(page=p, title="Services", author=skia, content=""" PageRev(page=p, title="Services", author=skia, content="""
| | | | | | | |
@ -252,83 +252,83 @@ Welcome to the wiki page!
# Adding README # Adding README
p = Page(name='README') p = Page(name='README')
p.save(force_lock=True) p.save(force_lock=True)
p.view_groups=[settings.SITH_GROUP_PUBLIC_ID] p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
p.save(force_lock=True) p.save(force_lock=True)
with open(os.path.join(root_path)+'/README.md', 'r') as rm: with open(os.path.join(root_path) + '/README.md', 'r') as rm:
PageRev(page=p, title="README", author=skia, content=rm.read()).save() PageRev(page=p, title="README", author=skia, content=rm.read()).save()
# Subscription # Subscription
## Root # Root
s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=root.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Skia # Skia
s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=skia.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Counter admin # Counter admin
s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=counter.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Comptable # Comptable
s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=comptable.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Richard # Richard
s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=r.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## User # User
s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Old subscriber # Old subscriber
s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=old_subscriber.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4)) s.subscription_start = s.compute_start(datetime(year=2012, month=9, day=4))
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
# Clubs # Clubs
Club(name="Bibo'UT", unix_name="bibout", Club(name="Bibo'UT", unix_name="bibout",
address="46 de la Boustifaille", parent=main_club).save() address="46 de la Boustifaille", parent=main_club).save()
guyut = Club(name="Guy'UT", unix_name="guyut", guyut = Club(name="Guy'UT", unix_name="guyut",
address="42 de la Boustifaille", parent=main_club) address="42 de la Boustifaille", parent=main_club)
guyut.save() guyut.save()
Club(name="Woenzel'UT", unix_name="woenzel", Club(name="Woenzel'UT", unix_name="woenzel",
address="Woenzel", parent=guyut).save() address="Woenzel", parent=guyut).save()
Membership(user=skia, club=main_club, role=3, description="").save() Membership(user=skia, club=main_club, role=3, description="").save()
troll = Club(name="Troll Penché", unix_name="troll", troll = Club(name="Troll Penché", unix_name="troll",
address="Terre Du Milieu", parent=main_club) address="Terre Du Milieu", parent=main_club)
troll.save() troll.save()
refound = Club(name="Carte AE", unix_name="carte_ae", refound = Club(name="Carte AE", unix_name="carte_ae",
address="Jamais imprimée", parent=main_club) address="Jamais imprimée", parent=main_club)
refound.save() refound.save()
# Counters # Counters
@ -341,19 +341,19 @@ Welcome to the wiki page!
r = ProductType(name="Rechargements") r = ProductType(name="Rechargements")
r.save() r.save()
cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15", cotis = Product(name="Cotis 1 semestre", code="1SCOTIZ", product_type=c, purchase_price="15", selling_price="15",
special_selling_price="15", club=main_club) special_selling_price="15", club=main_club)
cotis.save() cotis.save()
cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28", cotis2 = Product(name="Cotis 2 semestres", code="2SCOTIZ", product_type=c, purchase_price="28", selling_price="28",
special_selling_price="28", club=main_club) special_selling_price="28", club=main_club)
cotis2.save() cotis2.save()
refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15", refill = Product(name="Rechargement 15 €", code="15REFILL", product_type=r, purchase_price="15", selling_price="15",
special_selling_price="15", club=main_club) special_selling_price="15", club=main_club)
refill.save() refill.save()
barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7", barb = Product(name="Barbar", code="BARB", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club) special_selling_price="1.6", club=main_club)
barb.save() barb.save()
cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7", cble = Product(name="Chimay Bleue", code="CBLE", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club) special_selling_price="1.6", club=main_club)
cble.save() cble.save()
Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7", Product(name="Corsendonk", code="CORS", product_type=p, purchase_price="1.50", selling_price="1.7",
special_selling_price="1.6", club=main_club).save() special_selling_price="1.6", club=main_club).save()
@ -375,7 +375,7 @@ Welcome to the wiki page!
refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE') refound_counter = Counter(name="Carte AE", club=refound, type='OFFICE')
refound_counter.save() refound_counter.save()
refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0", refound_product = Product(name="remboursement", code="REMBOURS", purchase_price="0", selling_price="0",
special_selling_price="0", club=refound) special_selling_price="0", club=refound)
refound_product.save() refound_product.save()
# Accounting test values: # Accounting test values:
@ -397,28 +397,28 @@ Welcome to the wiki page!
buying.save() buying.save()
comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT') comptes = AccountingType(code='6', label="Comptes de charge", movement_type='DEBIT')
comptes.save() comptes.save()
simple = SimplifiedAccountingType(label = 'Je fais du simple 6', accounting_type = comptes, movement_type='DEBIT') simple = SimplifiedAccountingType(label='Je fais du simple 6', accounting_type=comptes, movement_type='DEBIT')
simple.save() simple.save()
woenzco = Company(name="Woenzel & co") woenzco = Company(name="Woenzel & co")
woenzco.save() woenzco.save()
operation_list = [ operation_list = [
(27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None), (27, "J'avais trop de bière", 'CASH', None, buying, 'USER', skia.id, "", None),
(4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit,'COMPANY', woenzco.id, "", 23), (4000, "Ceci n'est pas une opération... en fait si mais non", 'CHECK', None, debit, 'COMPANY', woenzco.id, "", 23),
(22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None), (22, "C'est de l'argent ?", 'CARD', None, credit, 'CLUB', troll.id, "", None),
(37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None), (37, "Je paye CASH", 'CASH', None, debit2, 'OTHER', None, "tous les étudiants <3", None),
(300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None), (300, "Paiement Guy", 'CASH', None, buying, 'USER', skia.id, "", None),
(32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None), (32.3, "Essence", 'CASH', None, buying, 'OTHER', None, "station", None),
(46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57), (46.42, "Allumette", 'CHECK', None, credit, 'CLUB', main_club.id, "", 57),
(666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None), (666.42, "Subvention de far far away", 'CASH', None, comptes, 'CLUB', main_club.id, "", None),
(496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None), (496, "Ça, c'est un 6", 'CARD', simple, None, 'USER', skia.id, "", None),
(17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None), (17, "La Gargotte du Korrigan", 'CASH', None, debit2, 'CLUB', bar_club.id, "", None),
] ]
for op in operation_list: for op in operation_list:
operation = Operation(journal=gj, date=date.today(), amount=op[0], operation = Operation(journal=gj, date=date.today(), amount=op[0],
remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3], remark=op[1], mode=op[2], done=True, simpleaccounting_type=op[3],
accounting_type=op[4], target_type=op[5], target_id=op[6], accounting_type=op[4], target_type=op[5], target_id=op[6],
target_label=op[7], cheque_number=op[8]) target_label=op[7], cheque_number=op[8])
operation.clean() operation.clean()
operation.save() operation.save()
@ -428,14 +428,14 @@ Welcome to the wiki page!
date_of_birth="1942-06-12") date_of_birth="1942-06-12")
sli.set_password("plop") sli.set_password("plop")
sli.save() sli.save()
sli.view_groups=[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id] sli.view_groups = [Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id]
sli.save() sli.save()
sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg') sli_profile_path = os.path.join(root_path, 'core/fixtures/images/5.jpg')
with open(sli_profile_path, 'rb') as f: with open(sli_profile_path, 'rb') as f:
name = str(sli.id) + "_profile.jpg" name = str(sli.id) + "_profile.jpg"
sli_profile = SithFile(parent=profiles_root, name=name, sli_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path)) owner=sli, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(sli_profile_path))
sli_profile.file.name = name sli_profile.file.name = name
sli_profile.save() sli_profile.save()
sli.profile_pict = sli_profile sli.profile_pict = sli_profile
@ -450,27 +450,27 @@ Welcome to the wiki page!
with open(krophil_profile_path, 'rb') as f: with open(krophil_profile_path, 'rb') as f:
name = str(krophil.id) + "_profile.jpg" name = str(krophil.id) + "_profile.jpg"
krophil_profile = SithFile(parent=profiles_root, name=name, krophil_profile = SithFile(parent=profiles_root, name=name,
file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'), file=resize_image(Image.open(BytesIO(f.read())), 400, 'JPEG'),
owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path)) owner=krophil, is_folder=False, mime_type='image/jpeg', size=os.path.getsize(krophil_profile_path))
krophil_profile.file.name = name krophil_profile.file.name = name
krophil_profile.save() krophil_profile.save()
krophil.profile_pict = krophil_profile krophil.profile_pict = krophil_profile
krophil.save() krophil.save()
## Adding subscription for sli # Adding subscription for sli
s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=sli.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
## Adding subscription for Krophil # Adding subscription for Krophil
s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0], s = Subscription(member=User.objects.filter(pk=krophil.pk).first(), subscription_type=list(settings.SITH_SUBSCRIPTIONS.keys())[0],
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0]) payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0])
s.subscription_start = s.compute_start() s.subscription_start = s.compute_start()
s.subscription_end = s.compute_end( s.subscription_end = s.compute_end(
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'], duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]['duration'],
start=s.subscription_start) start=s.subscription_start)
s.save() s.save()
# Add barman to counter # Add barman to counter
@ -483,8 +483,8 @@ Welcome to the wiki page!
subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP) subscriber_group = Group.objects.get(name=settings.SITH_MAIN_MEMBERS_GROUP)
ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP) ae_board_group = Group.objects.get(name=settings.SITH_MAIN_BOARD_GROUP)
el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01', el = Election(title="Élection 2017", description="La roue tourne", start_candidature='1942-06-12 10:28:45+01',
end_candidature='2042-06-12 10:28:45+01',start_date='1942-06-12 10:28:45+01', end_candidature='2042-06-12 10:28:45+01', start_date='1942-06-12 10:28:45+01',
end_date='7942-06-12 10:28:45+01') end_date='7942-06-12 10:28:45+01')
el.save() el.save()
el.view_groups.add(public_group) el.view_groups.add(public_group)
el.edit_groups.add(ae_board_group) el.edit_groups.add(ae_board_group)
@ -519,4 +519,3 @@ Welcome to the wiki page!
various.save() various.save()
Forum(name="Promos", description="Réservé aux Promos", parent=various).save() Forum(name="Promos", description="Réservé aux Promos", parent=various).save()
ForumTopic(forum=hall) ForumTopic(forum=hall)

View File

@ -23,9 +23,8 @@
# #
import os import os
from django.core.management.base import BaseCommand, CommandError from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.conf import settings
class Command(BaseCommand): class Command(BaseCommand):
@ -37,7 +36,7 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))) root_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
try: try:
os.mkdir(os.path.join(root_path)+'/data') os.mkdir(os.path.join(root_path) + '/data')
print("Data dir created") print("Data dir created")
except Exception as e: except Exception as e:
repr(e) repr(e)

View File

@ -24,7 +24,7 @@
import re import re
from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link
from django.core.urlresolvers import reverse_lazy, reverse from django.core.urlresolvers import reverse
class SithRenderer(Renderer): class SithRenderer(Renderer):
@ -54,13 +54,16 @@ class SithRenderer(Renderer):
src = original_src src = original_src
else: else:
width = m.group(1) width = m.group(1)
if not width.endswith('%'): width += "px" if not width.endswith('%'):
width += "px"
style = "width: %s; " % width style = "width: %s; " % width
try: try:
height = m.group(3) height = m.group(3)
if not height.endswith('%'): height += "px" if not height.endswith('%'):
height += "px"
style += "height: %s; " % height style += "height: %s; " % height
except: pass except:
pass
else: else:
params = None params = None
src = original_src src = original_src
@ -77,6 +80,7 @@ class SithRenderer(Renderer):
return '%s />' % html return '%s />' % html
return '%s>' % html return '%s>' % html
class SithInlineGrammar(InlineGrammar): class SithInlineGrammar(InlineGrammar):
double_emphasis = re.compile( double_emphasis = re.compile(
r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word** r'^\*{2}([\s\S]+?)\*{2}(?!\*)' # **word**
@ -94,6 +98,7 @@ class SithInlineGrammar(InlineGrammar):
r'^<sub>([\s\S]+?)</sub>' # <sub>text</sub> r'^<sub>([\s\S]+?)</sub>' # <sub>text</sub>
) )
class SithInlineLexer(InlineLexer): class SithInlineLexer(InlineLexer):
grammar_class = SithInlineGrammar grammar_class = SithInlineGrammar
@ -159,15 +164,16 @@ class SithInlineLexer(InlineLexer):
return self.renderer.emphasis(text) return self.renderer.emphasis(text)
def _process_link(self, m, link, title=None): def _process_link(self, m, link, title=None):
try: # Add page:// support for links try: # Add page:// support for links
page = re.compile( page = re.compile(
r'^page://(\S*)' # page://nom_de_ma_page r'^page://(\S*)' # page://nom_de_ma_page
) )
match = page.search(link) match = page.search(link)
page = match.group(1) or "" page = match.group(1) or ""
link = reverse('core:page', kwargs={'page_name': page}) link = reverse('core:page', kwargs={'page_name': page})
except: pass except:
try: # Add file:// support for links pass
try: # Add file:// support for links
file_link = re.compile( file_link = re.compile(
r'^file://(\d*)/?(\S*)?' # file://4000/download r'^file://(\d*)/?(\S*)?' # file://4000/download
) )
@ -175,9 +181,11 @@ class SithInlineLexer(InlineLexer):
id = match.group(1) id = match.group(1)
suffix = match.group(2) or "" suffix = match.group(2) or ""
link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix link = reverse('core:file_detail', kwargs={'file_id': id}) + suffix
except: pass except:
pass
return super(SithInlineLexer, self)._process_link(m, link, title) return super(SithInlineLexer, self)._process_link(m, link, title)
renderer = SithRenderer(escape=True) renderer = SithRenderer(escape=True)
inline = SithInlineLexer(renderer) inline = SithInlineLexer(renderer)
@ -222,4 +230,3 @@ Petit *test* _sur_ ^une^ **seule** ^ligne pour voir^
""" """
print(markdown(text)) print(markdown(text))

View File

@ -52,4 +52,3 @@ class AuthenticationMiddleware(DjangoAuthenticationMiddleware):
"'account.middleware.AuthenticationMiddleware'." "'account.middleware.AuthenticationMiddleware'."
) )
request.user = SimpleLazyObject(lambda: get_cached_user(request)) request.user = SimpleLazyObject(lambda: get_cached_user(request))

View File

@ -44,14 +44,17 @@ from datetime import datetime, timedelta, date
import unicodedata import unicodedata
class RealGroupManager(AuthGroupManager): class RealGroupManager(AuthGroupManager):
def get_queryset(self): def get_queryset(self):
return super(RealGroupManager, self).get_queryset().filter(is_meta=False) return super(RealGroupManager, self).get_queryset().filter(is_meta=False)
class MetaGroupManager(AuthGroupManager): class MetaGroupManager(AuthGroupManager):
def get_queryset(self): def get_queryset(self):
return super(MetaGroupManager, self).get_queryset().filter(is_meta=True) return super(MetaGroupManager, self).get_queryset().filter(is_meta=True)
class Group(AuthGroup): class Group(AuthGroup):
is_meta = models.BooleanField( is_meta = models.BooleanField(
_('meta group status'), _('meta group status'),
@ -69,8 +72,10 @@ class Group(AuthGroup):
""" """
return reverse('core:group_list') return reverse('core:group_list')
class MetaGroup(Group): class MetaGroup(Group):
objects = MetaGroupManager() objects = MetaGroupManager()
class Meta: class Meta:
proxy = True proxy = True
@ -78,20 +83,24 @@ class MetaGroup(Group):
super(MetaGroup, self).__init__(*args, **kwargs) super(MetaGroup, self).__init__(*args, **kwargs)
self.is_meta = True self.is_meta = True
class RealGroup(Group): class RealGroup(Group):
objects = RealGroupManager() objects = RealGroupManager()
class Meta: class Meta:
proxy = True proxy = True
def validate_promo(value): def validate_promo(value):
start_year = settings.SITH_SCHOOL_START_YEAR start_year = settings.SITH_SCHOOL_START_YEAR
delta = (date.today()+timedelta(days=180)).year - start_year delta = (date.today() + timedelta(days=180)).year - start_year
if value < 0 or delta < value: if value < 0 or delta < value:
raise ValidationError( raise ValidationError(
_('%(value)s is not a valid promo (between 0 and %(end)s)'), _('%(value)s is not a valid promo (between 0 and %(end)s)'),
params={'value': value, 'end': delta}, params={'value': value, 'end': delta},
) )
class User(AbstractBaseUser): class User(AbstractBaseUser):
""" """
Defines the base user class, useable in every app Defines the base user class, useable in every app
@ -148,13 +157,13 @@ class User(AbstractBaseUser):
) )
groups = models.ManyToManyField(RealGroup, related_name='users', blank=True) groups = models.ManyToManyField(RealGroup, related_name='users', blank=True)
home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True, home = models.OneToOneField('SithFile', related_name='home_of', verbose_name=_("home"), null=True, blank=True,
on_delete=models.SET_NULL) on_delete=models.SET_NULL)
profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True, profile_pict = models.OneToOneField('SithFile', related_name='profile_of', verbose_name=_("profile"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True, avatar_pict = models.OneToOneField('SithFile', related_name='avatar_of', verbose_name=_("avatar"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True, scrub_pict = models.OneToOneField('SithFile', related_name='scrub_of', verbose_name=_("scrub"), null=True,
blank=True, on_delete=models.SET_NULL) blank=True, on_delete=models.SET_NULL)
sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN") sex = models.CharField(_("sex"), max_length=10, choices=[("MAN", _("Man")), ("WOMAN", _("Woman"))], default="MAN")
tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[ tshirt_size = models.CharField(_("tshirt size"), max_length=5, choices=[
("-", _("-")), ("-", _("-")),
@ -165,7 +174,7 @@ class User(AbstractBaseUser):
("XL", _("XL")), ("XL", _("XL")),
("XXL", _("XXL")), ("XXL", _("XXL")),
("XXXL", _("XXXL")), ("XXXL", _("XXXL")),
], default="-") ], default="-")
role = models.CharField(_("role"), max_length=15, choices=[ role = models.CharField(_("role"), max_length=15, choices=[
("STUDENT", _("Student")), ("STUDENT", _("Student")),
("ADMINISTRATIVE", _("Administrative agent")), ("ADMINISTRATIVE", _("Administrative agent")),
@ -174,9 +183,9 @@ class User(AbstractBaseUser):
("DOCTOR", _("Doctor")), ("DOCTOR", _("Doctor")),
("FORMER STUDENT", _("Former student")), ("FORMER STUDENT", _("Former student")),
("SERVICE", _("Service")), ("SERVICE", _("Service")),
], blank=True, default="") ], blank=True, default="")
department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS, department = models.CharField(_("department"), max_length=15, choices=settings.SITH_PROFILE_DEPARTMENTS,
default="NA", blank=True) default="NA", blank=True)
dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="") dpt_option = models.CharField(_("dpt option"), max_length=32, blank=True, default="")
semester = models.CharField(_("semester"), max_length=5, blank=True, default="") semester = models.CharField(_("semester"), max_length=5, blank=True, default="")
quote = models.CharField(_("quote"), max_length=256, blank=True, default="") quote = models.CharField(_("quote"), max_length=256, blank=True, default="")
@ -226,11 +235,12 @@ class User(AbstractBaseUser):
_club_memberships = {} _club_memberships = {}
_group_names = {} _group_names = {}
_group_ids = {} _group_ids = {}
def is_in_group(self, group_name): def is_in_group(self, group_name):
"""If the user is in the group passed in argument (as string or by id)""" """If the user is in the group passed in argument (as string or by id)"""
group_id = 0 group_id = 0
g = None g = None
if isinstance(group_name, int): # Handle the case where group_name is an ID if isinstance(group_name, int): # Handle the case where group_name is an ID
if group_name in User._group_ids.keys(): if group_name in User._group_ids.keys():
g = User._group_ids[group_name] g = User._group_ids[group_name]
else: else:
@ -253,7 +263,7 @@ class User(AbstractBaseUser):
return self.is_subscribed return self.is_subscribed
if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID: if group_id == settings.SITH_GROUP_OLD_SUBSCRIBERS_ID:
return self.was_subscribed return self.was_subscribed
if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked if group_name == settings.SITH_MAIN_MEMBERS_GROUP: # We check the subscription if asked
return self.is_subscribed return self.is_subscribed
if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX: if group_name[-len(settings.SITH_BOARD_SUFFIX):] == settings.SITH_BOARD_SUFFIX:
name = group_name[:-len(settings.SITH_BOARD_SUFFIX)] name = group_name[:-len(settings.SITH_BOARD_SUFFIX)]
@ -315,7 +325,7 @@ class User(AbstractBaseUser):
else: else:
create = True create = True
super(User, self).save(*args, **kwargs) super(User, self).save(*args, **kwargs)
if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me! if create and settings.IS_OLD_MYSQL_PRESENT: # Create user on the old site: TODO remove me!
import MySQLdb import MySQLdb
try: try:
db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS) db = MySQLdb.connect(**settings.OLD_MYSQL_INFOS)
@ -324,9 +334,9 @@ class User(AbstractBaseUser):
(%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0")) (%s, %s, %s, %s, %s, %s)""", (self.id, self.last_name, self.first_name, self.email, "valid", "0"))
db.commit() db.commit()
except Exception as e: except Exception as e:
with open(settings.BASE_DIR+"/user_fail.log", "a") as f: with open(settings.BASE_DIR + "/user_fail.log", "a") as f:
print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name, print("FAIL to add user %s (%s %s - %s) to old site" % (self.id, self.first_name, self.last_name,
self.email), file=f) self.email), file=f)
print("Reason: %s" % (repr(e)), file=f) print("Reason: %s" % (repr(e)), file=f)
db.rollback() db.rollback()
@ -401,13 +411,13 @@ class User(AbstractBaseUser):
Returns the generated username Returns the generated username
""" """
def remove_accents(data): def remove_accents(data):
return ''.join(x for x in unicodedata.normalize('NFKD', data) if \ return ''.join(x for x in unicodedata.normalize('NFKD', data) if
unicodedata.category(x)[0] == 'L').lower() unicodedata.category(x)[0] == 'L').lower()
user_name = remove_accents(self.first_name[0]+self.last_name).encode('ascii', 'ignore').decode('utf-8') user_name = remove_accents(self.first_name[0] + self.last_name).encode('ascii', 'ignore').decode('utf-8')
un_set = [u.username for u in User.objects.all()] un_set = [u.username for u in User.objects.all()]
if user_name in un_set: if user_name in un_set:
i = 1 i = 1
while user_name+str(i) in un_set: while user_name + str(i) in un_set:
i += 1 i += 1
user_name += str(i) user_name += str(i)
self.username = user_name self.username = user_name
@ -473,7 +483,7 @@ class User(AbstractBaseUser):
self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"), self.profile_pict.get_download_url() if self.profile_pict else staticfiles_storage.url("core/img/unknown.jpg"),
_("Profile"), _("Profile"),
escape(self.get_display_name()), escape(self.get_display_name()),
) )
@cached_property @cached_property
def subscribed(self): def subscribed(self):
@ -489,6 +499,7 @@ class User(AbstractBaseUser):
infos.save() infos.save()
return infos return infos
class AnonymousUser(AuthAnonymousUser): class AnonymousUser(AuthAnonymousUser):
def __init__(self, request): def __init__(self, request):
super(AnonymousUser, self).__init__() super(AnonymousUser, self).__init__()
@ -530,7 +541,7 @@ class AnonymousUser(AuthAnonymousUser):
The anonymous user is only the public group The anonymous user is only the public group
""" """
group_id = 0 group_id = 0
if isinstance(group_name, int): # Handle the case where group_name is an ID if isinstance(group_name, int): # Handle the case where group_name is an ID
g = Group.objects.filter(id=group_name).first() g = Group.objects.filter(id=group_name).first()
if g: if g:
group_name = g.name group_name = g.name
@ -557,6 +568,7 @@ class AnonymousUser(AuthAnonymousUser):
def get_display_name(self): def get_display_name(self):
return _("Visitor") return _("Visitor")
class Preferences(models.Model): class Preferences(models.Model):
user = models.OneToOneField(User, related_name="preferences") user = models.OneToOneField(User, related_name="preferences")
receive_weekmail = models.BooleanField( receive_weekmail = models.BooleanField(
@ -576,15 +588,19 @@ class Preferences(models.Model):
def get_absolute_url(self): def get_absolute_url(self):
return self.user.get_absolute_url() return self.user.get_absolute_url()
def get_directory(instance, filename): def get_directory(instance, filename):
return '.{0}/{1}'.format(instance.get_parent_path(), filename) return '.{0}/{1}'.format(instance.get_parent_path(), filename)
def get_compressed_directory(instance, filename): def get_compressed_directory(instance, filename):
return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename) return '.{0}/compressed/{1}'.format(instance.get_parent_path(), filename)
def get_thumbnail_directory(instance, filename): def get_thumbnail_directory(instance, filename):
return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename) return '.{0}/thumbnail/{1}'.format(instance.get_parent_path(), filename)
class SithFile(models.Model): class SithFile(models.Model):
name = models.CharField(_('file name'), max_length=256, blank=False) name = models.CharField(_('file name'), max_length=256, blank=False)
parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True) parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True)
@ -601,7 +617,7 @@ class SithFile(models.Model):
is_moderated = models.BooleanField(_("is moderated"), default=False) is_moderated = models.BooleanField(_("is moderated"), default=False)
moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True) moderator = models.ForeignKey(User, related_name="moderated_files", verbose_name=_("owner"), null=True, blank=True)
asked_for_removal = models.BooleanField(_("asked for removal"), default=False) asked_for_removal = models.BooleanField(_("asked for removal"), default=False)
is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save() is_in_sas = models.BooleanField(_("is in the SAS"), default=False) # Allows to query this flag, updated at each call to save()
class Meta: class Meta:
verbose_name = _("file") verbose_name = _("file")
@ -763,18 +779,22 @@ class SithFile(models.Model):
def __str__(self): def __str__(self):
return self.get_parent_path() + "/" + self.name return self.get_parent_path() + "/" + self.name
class LockError(Exception): class LockError(Exception):
"""There was a lock error on the object""" """There was a lock error on the object"""
pass pass
class AlreadyLocked(LockError): class AlreadyLocked(LockError):
"""The object is already locked""" """The object is already locked"""
pass pass
class NotLocked(LockError): class NotLocked(LockError):
"""The object is not locked""" """The object is not locked"""
pass pass
class Page(models.Model): class Page(models.Model):
""" """
The page class to build a Wiki The page class to build a Wiki
@ -787,14 +807,13 @@ class Page(models.Model):
query, but don't rely on it when playing with a Page object, use get_full_name() instead! query, but don't rely on it when playing with a Page object, use get_full_name() instead!
""" """
name = models.CharField(_('page unix name'), max_length=30, name = models.CharField(_('page unix name'), max_length=30,
validators=[ validators=[
validators.RegexValidator( validators.RegexValidator(
r'^[A-z.+-]+$', r'^[A-z.+-]+$',
_('Enter a valid page name. This value may contain only ' _('Enter a valid page name. This value may contain only '
'unaccented letters, numbers ' 'and ./+/-/_ characters.') 'unaccented letters, numbers ' 'and ./+/-/_ characters.')
), ), ],
], blank=False)
blank=False)
parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL) parent = models.ForeignKey('self', related_name="children", verbose_name=_("parent"), null=True, blank=True, on_delete=models.SET_NULL)
# Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when # Attention: this field may not be valid until you call save(). It's made for fast query, but don't rely on it when
# playing with a Page object, use get_full_name() instead! # playing with a Page object, use get_full_name() instead!
@ -854,12 +873,13 @@ class Page(models.Model):
Performs some needed actions before and after saving a page in database Performs some needed actions before and after saving a page in database
""" """
locked = kwargs.pop('force_lock', False) locked = kwargs.pop('force_lock', False)
if not locked: locked = self.is_locked() if not locked:
locked = self.is_locked()
if not locked: if not locked:
raise NotLocked("The page is not locked and thus can not be saved") raise NotLocked("The page is not locked and thus can not be saved")
self.full_clean() self.full_clean()
if not self.id: if not self.id:
super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name super(Page, self).save(*args, **kwargs) # Save a first time to correctly set _full_name
# This reset the _full_name just before saving to maintain a coherent field quicker for queries than the # This reset the _full_name just before saving to maintain a coherent field quicker for queries than the
# recursive method # recursive method
# It also update all the children to maintain correct names # It also update all the children to maintain correct names
@ -973,7 +993,7 @@ class PageRev(models.Model):
page = models.ForeignKey(Page, related_name='revisions') page = models.ForeignKey(Page, related_name='revisions')
class Meta: class Meta:
ordering = ['date',] ordering = ['date', ]
def get_absolute_url(self): def get_absolute_url(self):
""" """
@ -1003,6 +1023,7 @@ class PageRev(models.Model):
# Don't forget to unlock, otherwise, people will have to wait for the page's timeout # Don't forget to unlock, otherwise, people will have to wait for the page's timeout
self.page.unset_lock() self.page.unset_lock()
class Notification(models.Model): class Notification(models.Model):
user = models.ForeignKey(User, related_name='notifications') user = models.ForeignKey(User, related_name='notifications')
url = models.CharField(_("url"), max_length=255) url = models.CharField(_("url"), max_length=255)
@ -1015,4 +1036,3 @@ class Notification(models.Model):
if self.param: if self.param:
return self.get_type_display() % self.param return self.get_type_display() % self.param
return self.get_type_display() return self.get_type_display()

View File

@ -27,30 +27,32 @@ from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from core.scss.processor import ScssProcessor from core.scss.processor import ScssProcessor
from django.utils.html import escape
from core.markdown import markdown as md from core.markdown import markdown as md
register = template.Library() register = template.Library()
@register.filter(is_safe=False) @register.filter(is_safe=False)
@stringfilter @stringfilter
def markdown(text): def markdown(text):
return mark_safe("<div class=\"markdown\">%s</div>" % md(text)) return mark_safe("<div class=\"markdown\">%s</div>" % md(text))
@register.filter() @register.filter()
@stringfilter @stringfilter
def datetime_format_python_to_PHP(python_format_string): def datetime_format_python_to_PHP(python_format_string):
""" """
Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible. Given a python datetime format string, attempts to convert it to the nearest PHP datetime format string possible.
""" """
python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e" } python2PHP = {"%a": "D", "%a": "D", "%A": "l", "%b": "M", "%B": "F", "%c": "", "%d": "d", "%H": "H", "%I": "h", "%j": "z", "%m": "m", "%M": "i", "%p": "A", "%S": "s", "%U": "", "%w": "w", "%W": "W", "%x": "", "%X": "", "%y": "y", "%Y": "Y", "%Z": "e"}
php_format_string = python_format_string php_format_string = python_format_string
for py, php in python2PHP.items(): for py, php in python2PHP.items():
php_format_string = php_format_string.replace(py, php) php_format_string = php_format_string.replace(py, php)
return php_format_string return php_format_string
@register.simple_tag() @register.simple_tag()
def scss(path): def scss(path):
""" """

View File

@ -24,7 +24,6 @@
from django.test import Client, TestCase from django.test import Client, TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.models import Group
from django.core.management import call_command from django.core.management import call_command
from core.models import User, Group, Page from core.models import User, Group, Page
@ -34,6 +33,7 @@ to run these tests :
python3 manage.py test python3 manage.py test
""" """
class UserRegistrationTest(TestCase): class UserRegistrationTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -52,7 +52,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_OK' in str(response.content))
@ -67,7 +67,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop2', 'password2': 'plop2',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -82,7 +82,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -97,7 +97,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -112,7 +112,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '', 'date_of_birth': '',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -127,7 +127,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -142,14 +142,14 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:register'), {'first_name': 'Bibou', response = c.post(reverse('core:register'), {'first_name': 'Bibou',
'last_name': 'Carlier', 'last_name': 'Carlier',
'email': 'bibou@git.an', 'email': 'bibou@git.an',
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content)) self.assertTrue('TEST_REGISTER_USER_FORM_FAIL' in str(response.content))
@ -164,7 +164,7 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'}) response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'plop'})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
#self.assertTrue('Hello, world' in str(response.content)) #self.assertTrue('Hello, world' in str(response.content))
@ -180,11 +180,12 @@ class UserRegistrationTest(TestCase):
'date_of_birth': '12/6/1942', 'date_of_birth': '12/6/1942',
'password1': 'plop', 'password1': 'plop',
'password2': 'plop', 'password2': 'plop',
}) })
response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'}) response = c.post(reverse('core:login'), {'username': 'gcarlier', 'password': 'guy'})
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>""" in str(response.content)) self.assertTrue("""<p>Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>""" in str(response.content))
class PageHandlingTest(TestCase): class PageHandlingTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -207,7 +208,7 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': 1, 'owner_group': 1,
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/hist">' in str(response.content)) self.assertTrue('<a href="/page/guy/hist">' in str(response.content))
@ -220,12 +221,12 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': '1', 'owner_group': '1',
}) })
response = self.client.post(reverse('core:page_new'), { response = self.client.post(reverse('core:page_new'), {
'parent': '1', 'parent': '1',
'name': 'bibou', 'name': 'bibou',
'owner_group': '1', 'owner_group': '1',
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy/bibou'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content)) self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content))
@ -259,11 +260,11 @@ class PageHandlingTest(TestCase):
'parent': '', 'parent': '',
'name': 'guy', 'name': 'guy',
'owner_group': '1', 'owner_group': '1',
}) })
r = self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), { self.client.post(reverse('core:page_edit', kwargs={'page_name': 'guy'}), {
'title': 'Bibou', 'title': 'Bibou',
'content': 'content':
'''Guy *bibou* '''Guy *bibou*
http://git.an http://git.an
@ -273,18 +274,19 @@ http://git.an
<script>alert('Guy');</script> <script>alert('Guy');</script>
''', ''',
}) })
response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'})) response = self.client.get(reverse('core:page', kwargs={'page_name': 'guy'}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' + self.assertTrue('<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' +
'<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' + '<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;' +
"&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content)) "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" in str(response.content))
#TODO: many tests on the pages: # TODO: many tests on the pages:
# - renaming a page # - renaming a page
# - changing a page's parent --> check that page's children's full_name # - changing a page's parent --> check that page's children's full_name
# - changing the different groups of the page # - changing the different groups of the page
class FileHandlingTest(TestCase): class FileHandlingTest(TestCase):
def setUp(self): def setUp(self):
try: try:
@ -295,19 +297,18 @@ class FileHandlingTest(TestCase):
print(e) print(e)
def test_create_folder_home(self): def test_create_folder_home(self):
response = self.client.post(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id}), response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"}) {"folder_name": "GUY_folder_test"})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id})) response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("GUY_folder_test</a>" in str(response.content)) self.assertTrue("GUY_folder_test</a>" in str(response.content))
def test_upload_file_home(self): def test_upload_file_home(self):
with open("/bin/ls", "rb") as f: with open("/bin/ls", "rb") as f:
response = self.client.post(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id}), response = self.client.post(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"file_field": f}) {"file_field": f})
self.assertTrue(response.status_code == 302) self.assertTrue(response.status_code == 302)
response = self.client.get(reverse("core:file_detail", kwargs={"file_id":self.subscriber.home.id})) response = self.client.get(reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}))
self.assertTrue(response.status_code == 200) self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content)) self.assertTrue("ls</a>" in str(response.content))

View File

@ -22,7 +22,7 @@
# #
# #
from django.conf.urls import url, include from django.conf.urls import url
from core.views import * from core.views import *

View File

@ -27,9 +27,10 @@ import re
# Image utils # Image utils
from io import BytesIO from io import BytesIO
from datetime import datetime, timezone, date from datetime import date
from PIL import Image, ExifTags from PIL import ExifTags
# from exceptions import IOError
import PIL import PIL
from django.conf import settings from django.conf import settings
@ -49,16 +50,17 @@ def get_start_of_semester(d=date.today()):
today = d today = d
year = today.year year = today.year
start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1]) start = date(year, settings.SITH_START_DATE[0], settings.SITH_START_DATE[1])
start2 = start.replace(month=(start.month+6)%12) start2 = start.replace(month=(start.month + 6) % 12)
if start > start2: if start > start2:
start, start2 = start2, start start, start2 = start2, start
if today < start: if today < start:
return start2.replace(year=year-1) return start2.replace(year=year - 1)
elif today < start2: elif today < start2:
return start return start
else: else:
return start2 return start2
def get_semester(d=date.today()): def get_semester(d=date.today()):
start = get_start_of_semester(d) start = get_start_of_semester(d)
if start.month <= 6: if start.month <= 6:
@ -66,6 +68,7 @@ def get_semester(d=date.today()):
else: else:
return "A" + str(start.year)[-2:] return "A" + str(start.year)[-2:]
def scale_dimension(width, height, long_edge): def scale_dimension(width, height, long_edge):
if width > height: if width > height:
ratio = long_edge * 1. / width ratio = long_edge * 1. / width
@ -73,6 +76,7 @@ def scale_dimension(width, height, long_edge):
ratio = long_edge * 1. / height ratio = long_edge * 1. / height
return int(width * ratio), int(height * ratio) return int(width * ratio), int(height * ratio)
def resize_image(im, edge, format): def resize_image(im, edge, format):
(w, h) = im.size (w, h) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge) (width, height) = scale_dimension(w, h, long_edge=edge)
@ -85,28 +89,31 @@ def resize_image(im, edge, format):
im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True) im.save(fp=content, format=format.upper(), quality=90, optimize=True, progressive=True)
return ContentFile(content.getvalue()) return ContentFile(content.getvalue())
def exif_auto_rotate(image):
for orientation in ExifTags.TAGS.keys() :
if ExifTags.TAGS[orientation]=='Orientation' : break
exif=dict(image._getexif().items())
if exif[orientation] == 3 : def exif_auto_rotate(image):
image=image.rotate(180, expand=True) for orientation in ExifTags.TAGS.keys():
elif exif[orientation] == 6 : if ExifTags.TAGS[orientation] == 'Orientation':
image=image.rotate(270, expand=True) break
elif exif[orientation] == 8 : exif = dict(image._getexif().items())
image=image.rotate(90, expand=True)
if exif[orientation] == 3:
image = image.rotate(180, expand=True)
elif exif[orientation] == 6:
image = image.rotate(270, expand=True)
elif exif[orientation] == 8:
image = image.rotate(90, expand=True)
return image return image
def doku_to_markdown(text): def doku_to_markdown(text):
"""This is a quite correct doku translator""" """This is a quite correct doku translator"""
text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict) text = re.sub(r'([^:]|^)\/\/(.*?)\/\/', r'*\2*', text) # Italic (prevents protocol:// conflict)
text = re.sub(r'<del>(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(r'<del>(.*?)<\/del>', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline)
text = re.sub(r'<sup>(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used) text = re.sub(r'<sup>(.*?)<\/sup>', r'^\1^', text) # Superscript (multiline not supported, because almost never used)
text = re.sub(r'<sub>(.*?)<\/sub>', r'_\1_', text) # Subscript (idem) text = re.sub(r'<sub>(.*?)<\/sub>', r'_\1_', text) # Subscript (idem)
text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles text = re.sub(r'^======(.*?)======', r'#\1', text, flags=re.MULTILINE) # Titles
text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE) text = re.sub(r'^=====(.*?)=====', r'##\1', text, flags=re.MULTILINE)
text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE) text = re.sub(r'^====(.*?)====', r'###\1', text, flags=re.MULTILINE)
text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE) text = re.sub(r'^===(.*?)===', r'####\1', text, flags=re.MULTILINE)
@ -121,93 +128,95 @@ def doku_to_markdown(text):
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r'dfile://', r'file://', text)
i = 1 i = 1
for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes for fn in re.findall(r'\(\((.*?)\)\)', text): # Footnotes
text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1) text = re.sub(r'\(\((.*?)\)\)', r'[^%s]' % i, text, count=1)
text += "\n[^%s]: %s\n" % (i, fn) text += "\n[^%s]: %s\n" % (i, fn)
i += 1 i += 1
text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return text = re.sub(r'\\{2,}[\s]', r' \n', text) # Carriage return
text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links text = re.sub(r'\[\[(.*?)\|(.*?)\]\]', r'[\2](\1)', text) # Links
text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2 text = re.sub(r'\[\[(.*?)\]\]', r'[\1](\1)', text) # Links 2
text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images text = re.sub(r'{{(.*?)\|(.*?)}}', r'![\2](\1 "\2")', text) # Images
text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2 text = re.sub(r'{{(.*?)(\|(.*?))?}}', r'![\1](\1 "\1")', text) # Images 2
text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them) text = re.sub(r'{\[(.*?)(\|(.*?))?\]}', r'[\1](\1)', text) # Video (transform to classic links, since we can't integrate them)
text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar text = re.sub(r'###(\d*?)###', r'[[[\1]]]', text) # Progress bar
text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists text = re.sub(r'(\n +[^* -][^\n]*(\n +[^* -][^\n]*)*)', r'```\1\n```', text, flags=re.DOTALL) # Block code without lists
text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists text = re.sub(r'( +)-(.*)', r'1.\2', text) # Ordered lists
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r'\[quote(=(.+?))?\]', line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r'\[/quote\]', line)
if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part if re.search(r'\A\s*\^(([^\^]*?)\^)*', line): # Table part
line = line.replace('^', '|') line = line.replace('^', '|')
new_text.append("> " * quote_level + line) new_text.append("> " * quote_level + line)
new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth new_text.append("> " * quote_level + "|---|") # Don't keep the text alignement in tables it's really too complex for what it's worth
elif enter or quit: # Quote part elif enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
try: try:
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)
return "\n".join(new_text) return "\n".join(new_text)
def bbcode_to_markdown(text): def bbcode_to_markdown(text):
"""This is a very basic BBcode translator""" """This is a very basic BBcode translator"""
text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold text = re.sub(r'\[b\](.*?)\[\/b\]', r'**\1**', text, flags=re.DOTALL) # Bold
text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic text = re.sub(r'\[i\](.*?)\[\/i\]', r'*\1*', text, flags=re.DOTALL) # Italic
text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline text = re.sub(r'\[u\](.*?)\[\/u\]', r'__\1__', text, flags=re.DOTALL) # Underline
text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline) text = re.sub(r'\[s\](.*?)\[\/s\]', r'~~\1~~', text, flags=re.DOTALL) # Strike (may be multiline)
text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2 text = re.sub(r'\[strike\](.*?)\[\/strike\]', r'~~\1~~', text, flags=re.DOTALL) # Strike 2
text = re.sub(r'article://', r'page://', text) text = re.sub(r'article://', r'page://', text)
text = re.sub(r'dfile://', r'file://', text) text = re.sub(r'dfile://', r'file://', text)
text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links text = re.sub(r'\[url=(.*?)\](.*)\[\/url\]', r'[\2](\1)', text) # Links
text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2 text = re.sub(r'\[url\](.*)\[\/url\]', r'\1', text) # Links 2
text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images text = re.sub(r'\[img\](.*)\[\/img\]', r'![\1](\1 "\1")', text) # Images
new_text = [] new_text = []
quote_level = 0 quote_level = 0
for line in text.splitlines(): # Tables and quotes for line in text.splitlines(): # Tables and quotes
enter = re.finditer(r'\[quote(=(.+?))?\]', line) enter = re.finditer(r'\[quote(=(.+?))?\]', line)
quit = re.finditer(r'\[/quote\]', line) quit = re.finditer(r'\[/quote\]', line)
if enter or quit: # Quote part if enter or quit: # Quote part
for quote in enter: # Enter quotes (support multiple at a time) for quote in enter: # Enter quotes (support multiple at a time)
quote_level += 1 quote_level += 1
try: try:
new_text.append("> " * quote_level + "##### " + quote.group(2)) new_text.append("> " * quote_level + "##### " + quote.group(2))
except: except:
new_text.append("> " * quote_level) new_text.append("> " * quote_level)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
final_newline = False final_newline = False
for quote in quit: # Quit quotes (support multiple at a time) for quote in quit: # Quit quotes (support multiple at a time)
line = line.replace(quote.group(0), '') line = line.replace(quote.group(0), '')
quote_level -= 1 quote_level -= 1
final_newline = True final_newline = True
new_text.append("> " * final_quote_level + line) # Finally append the line new_text.append("> " * final_quote_level + line) # Finally append the line
if final_newline: new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text if final_newline:
new_text.append("\n") # Add a new line to ensure the separation between the quote and the following text
else: else:
new_text.append(line) new_text.append(line)
return "\n".join(new_text) return "\n".join(new_text)

View File

@ -23,29 +23,28 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import redirect
from django.views.generic import ListView, DetailView, TemplateView from django.views.generic import ListView, DetailView, TemplateView
from django.views.generic.edit import UpdateView, CreateView, FormMixin, DeleteView from django.views.generic.edit import UpdateView, FormMixin, DeleteView
from django.views.generic.detail import SingleObjectMixin from django.views.generic.detail import SingleObjectMixin
from django.contrib.auth.decorators import login_required, permission_required
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.http import HttpResponse from django.http import HttpResponse
from django.core.servers.basehttp import FileWrapper from django.core.servers.basehttp import FileWrapper
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist from django.core.exceptions import PermissionDenied
from django import forms from django import forms
import os import os
from ajax_select import make_ajax_form, make_ajax_field from ajax_select import make_ajax_field
from core.models import SithFile, RealGroup, Notification from core.models import SithFile, RealGroup, Notification
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin, can_view, not_found from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, can_view, not_found
from counter.models import Counter from counter.models import Counter
def send_file(request, file_id, file_class=SithFile, file_attr="file"): def send_file(request, file_id, file_class=SithFile, file_attr="file"):
""" """
Send a file through Django without loading the whole file into Send a file through Django without loading the whole file into
@ -57,7 +56,7 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
return not_found(request) return not_found(request)
if not (can_view(f, request.user) or if not (can_view(f, request.user) or
('counter_token' in request.session.keys() and ('counter_token' in request.session.keys() and
request.session['counter_token'] and # check if not null for counters that have no token set request.session['counter_token'] and # check if not null for counters that have no token set
Counter.objects.filter(token=request.session['counter_token']).exists()) Counter.objects.filter(token=request.session['counter_token']).exists())
): ):
raise PermissionDenied raise PermissionDenied
@ -70,10 +69,11 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8') response['Content-Disposition'] = ('inline; filename="%s"' % f.name).encode('utf-8')
return response return response
class AddFilesForm(forms.Form): class AddFilesForm(forms.Form):
folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False) folder_name = forms.CharField(label=_("Add a new folder"), max_length=30, required=False)
file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"), file_field = forms.FileField(widget=forms.ClearableFileInput(attrs={'multiple': True}), label=_("Files"),
required=False) required=False)
def process(self, parent, owner, files): def process(self, parent, owner, files):
notif = False notif = False
@ -85,10 +85,10 @@ class AddFilesForm(forms.Form):
notif = True notif = True
except Exception as e: except Exception as e:
self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") % self.add_error(None, _("Error creating folder %(folder_name)s: %(msg)s") %
{'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)}) {'folder_name': self.cleaned_data['folder_name'], 'msg': repr(e)})
for f in files: for f in files:
new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False, new_file = SithFile(parent=parent, name=f.name, file=f, owner=owner, is_folder=False,
mime_type=f.content_type, size=f._size) mime_type=f.content_type, size=f._size)
try: try:
new_file.clean() new_file.clean()
new_file.save() new_file.save()
@ -100,6 +100,7 @@ class AddFilesForm(forms.Form):
if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists(): if not u.notifications.filter(type="FILE_MODERATION", viewed=False).exists():
Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save() Notification(user=u, url=reverse("core:file_moderation"), type="FILE_MODERATION").save()
class FileListView(ListView): class FileListView(ListView):
template_name = 'core/file_list.jinja' template_name = 'core/file_list.jinja'
context_object_name = "file_list" context_object_name = "file_list"
@ -114,6 +115,7 @@ class FileListView(ListView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileEditView(CanEditMixin, UpdateView): class FileEditView(CanEditMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -138,6 +140,7 @@ class FileEditView(CanEditMixin, UpdateView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileEditPropForm(forms.ModelForm): class FileEditPropForm(forms.ModelForm):
class Meta: class Meta:
model = SithFile model = SithFile
@ -147,6 +150,7 @@ class FileEditPropForm(forms.ModelForm):
view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group")) view_groups = make_ajax_field(SithFile, 'view_groups', 'groups', help_text="", label=_("view group"))
recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False) recursive = forms.BooleanField(label=_("Apply rights recursively"), required=False)
class FileEditPropView(CanEditPropMixin, UpdateView): class FileEditPropView(CanEditPropMixin, UpdateView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -175,6 +179,7 @@ class FileEditPropView(CanEditPropMixin, UpdateView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileView(CanViewMixin, DetailView, FormMixin): class FileView(CanViewMixin, DetailView, FormMixin):
"""This class handle the upload of new files into a folder""" """This class handle the upload of new files into a folder"""
model = SithFile model = SithFile
@ -217,7 +222,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
request.session['clipboard'] = [] request.session['clipboard'] = []
if request.user.can_edit(self.object): if request.user.can_edit(self.object):
FileView.handle_clipboard(request, self.object) FileView.handle_clipboard(request, self.object)
self.form = self.get_form() # The form handle only the file upload self.form = self.get_form() # The form handle only the file upload
files = request.FILES.getlist('file_field') files = request.FILES.getlist('file_field')
if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid(): if request.user.is_authenticated() and request.user.can_edit(self.object) and self.form.is_valid():
self.form.process(parent=self.object, owner=request.user, files=files) self.form.process(parent=self.object, owner=request.user, files=files)
@ -237,6 +242,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard']) kwargs['clipboard'] = SithFile.objects.filter(id__in=self.request.session['clipboard'])
return kwargs return kwargs
class FileDeleteView(CanEditPropMixin, DeleteView): class FileDeleteView(CanEditPropMixin, DeleteView):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -244,7 +250,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
context_object_name = "file" context_object_name = "file"
def get_success_url(self): def get_success_url(self):
self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here self.object.file.delete() # Doing it here or overloading delete() is the same, so let's do it here
if 'next' in self.request.GET.keys(): if 'next' in self.request.GET.keys():
return self.request.GET['next'] return self.request.GET['next']
if self.object.parent is None: if self.object.parent is None:
@ -258,6 +264,7 @@ class FileDeleteView(CanEditPropMixin, DeleteView):
kwargs['popup'] = 'popup' kwargs['popup'] = 'popup'
return kwargs return kwargs
class FileModerationView(TemplateView): class FileModerationView(TemplateView):
template_name = "core/file_moderation.jinja" template_name = "core/file_moderation.jinja"
@ -266,6 +273,7 @@ class FileModerationView(TemplateView):
kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100] kwargs['files'] = SithFile.objects.filter(is_moderated=False)[:100]
return kwargs return kwargs
class FileModerateView(CanEditPropMixin, SingleObjectMixin): class FileModerateView(CanEditPropMixin, SingleObjectMixin):
model = SithFile model = SithFile
pk_url_kwarg = "file_id" pk_url_kwarg = "file_id"
@ -278,4 +286,3 @@ class FileModerateView(CanEditPropMixin, SingleObjectMixin):
if 'next' in self.request.GET.keys(): if 'next' in self.request.GET.keys():
return redirect(self.request.GET['next']) return redirect(self.request.GET['next'])
return redirect('core:file_moderation') return redirect('core:file_moderation')

View File

@ -22,22 +22,24 @@
# #
# #
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, UserChangeForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db import transaction from django.db import transaction
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.contrib.auth import logout, login, authenticate
from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea from django.forms import CheckboxSelectMultiple, Select, DateInput, TextInput, DateTimeInput, Textarea
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext from django.utils.translation import ugettext
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
import logging
import re import re
from core.models import User, Page, RealGroup, SithFile from core.models import User, Page, SithFile
from core.utils import resize_image
from io import BytesIO
from PIL import Image
# Widgets # Widgets
@ -50,6 +52,7 @@ class SelectSingle(Select):
attrs = {'class': "select_single"} attrs = {'class': "select_single"}
return super(SelectSingle, self).render(name, value, attrs) return super(SelectSingle, self).render(name, value, attrs)
class SelectMultiple(Select): class SelectMultiple(Select):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -58,6 +61,7 @@ class SelectMultiple(Select):
attrs = {'class': "select_multiple"} attrs = {'class': "select_multiple"}
return super(SelectMultiple, self).render(name, value, attrs) return super(SelectMultiple, self).render(name, value, attrs)
class SelectDateTime(DateTimeInput): class SelectDateTime(DateTimeInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -66,6 +70,7 @@ class SelectDateTime(DateTimeInput):
attrs = {'class': "select_datetime"} attrs = {'class': "select_datetime"}
return super(SelectDateTime, self).render(name, value, attrs) return super(SelectDateTime, self).render(name, value, attrs)
class SelectDate(DateInput): class SelectDate(DateInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -74,16 +79,18 @@ class SelectDate(DateInput):
attrs = {'class': "select_date"} attrs = {'class': "select_date"}
return super(SelectDate, self).render(name, value, attrs) return super(SelectDate, self).render(name, value, attrs)
class MarkdownInput(Textarea): class MarkdownInput(Textarea):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
output = '<p><a href="%(syntax_url)s">%(help_text)s</a></p>'\ output = '<p><a href="%(syntax_url)s">%(help_text)s</a></p>'\
'<div class="markdown_editor">%(content)s</div>' % { '<div class="markdown_editor">%(content)s</div>' % {
'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(), 'syntax_url': Page.get_page_by_full_name(settings.SITH_CORE_PAGE_SYNTAX).get_absolute_url(),
'help_text': _("Help on the syntax"), 'help_text': _("Help on the syntax"),
'content': super(MarkdownInput, self).render(name, value, attrs), 'content': super(MarkdownInput, self).render(name, value, attrs),
} }
return output return output
class SelectFile(TextInput): class SelectFile(TextInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -91,13 +98,14 @@ class SelectFile(TextInput):
else: else:
attrs = {'class': "select_file"} attrs = {'class': "select_file"}
output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % { output = '%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>' % {
'content': super(SelectFile, self).render(name, value, attrs), 'content': super(SelectFile, self).render(name, value, attrs),
'title': _("Choose file"), 'title': _("Choose file"),
'name': name, 'name': name,
} }
output += '<span name="' + name + '" class="choose_file_button">' + ugettext("Choose file") + '</span>' output += '<span name="' + name + '" class="choose_file_button">' + ugettext("Choose file") + '</span>'
return output return output
class SelectUser(TextInput): class SelectUser(TextInput):
def render(self, name, value, attrs=None): def render(self, name, value, attrs=None):
if attrs: if attrs:
@ -105,15 +113,16 @@ class SelectUser(TextInput):
else: else:
attrs = {'class': "select_user"} attrs = {'class': "select_user"}
output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % { output = '%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>' % {
'content': super(SelectUser, self).render(name, value, attrs), 'content': super(SelectUser, self).render(name, value, attrs),
'title': _("Choose user"), 'title': _("Choose user"),
'name': name, 'name': name,
} }
output += '<span name="' + name + '" class="choose_user_button">' + ugettext("Choose user") + '</span>' output += '<span name="' + name + '" class="choose_user_button">' + ugettext("Choose user") + '</span>'
return output return output
# Forms # Forms
class LoginForm(AuthenticationForm): class LoginForm(AuthenticationForm):
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
if 'data' in kwargs.keys(): if 'data' in kwargs.keys():
@ -128,14 +137,17 @@ class LoginForm(AuthenticationForm):
else: else:
user = User.objects.filter(username=data['username']).first() user = User.objects.filter(username=data['username']).first()
data['username'] = user.username data['username'] = user.username
except: pass except:
pass
kwargs['data'] = data kwargs['data'] = data
super(LoginForm, self).__init__(*arg, **kwargs) super(LoginForm, self).__init__(*arg, **kwargs)
self.fields['username'].label = _("Username, email, or account number") self.fields['username'].label = _("Username, email, or account number")
class RegisteringForm(UserCreationForm): class RegisteringForm(UserCreationForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = User model = User
fields = ('first_name', 'last_name', 'email') fields = ('first_name', 'last_name', 'email')
@ -148,9 +160,6 @@ class RegisteringForm(UserCreationForm):
user.save() user.save()
return user return user
from core.utils import resize_image
from io import BytesIO
from PIL import Image
class UserProfileForm(forms.ModelForm): class UserProfileForm(forms.ModelForm):
""" """
@ -161,22 +170,22 @@ class UserProfileForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict', fields = ['first_name', 'last_name', 'nick_name', 'email', 'date_of_birth', 'profile_pict', 'avatar_pict',
'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone', 'scrub_pict', 'sex', 'second_email', 'address', 'parent_address', 'phone', 'parent_phone',
'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo', 'tshirt_size', 'role', 'department', 'dpt_option', 'semester', 'quote', 'school', 'promo',
'forum_signature', 'is_subscriber_viewable'] 'forum_signature', 'is_subscriber_viewable']
widgets = { widgets = {
'date_of_birth': SelectDate, 'date_of_birth': SelectDate,
'profile_pict': forms.ClearableFileInput, 'profile_pict': forms.ClearableFileInput,
'avatar_pict': forms.ClearableFileInput, 'avatar_pict': forms.ClearableFileInput,
'scrub_pict': forms.ClearableFileInput, 'scrub_pict': forms.ClearableFileInput,
'phone': PhoneNumberInternationalFallbackWidget, 'phone': PhoneNumberInternationalFallbackWidget,
'parent_phone': PhoneNumberInternationalFallbackWidget, 'parent_phone': PhoneNumberInternationalFallbackWidget,
} }
labels = { labels = {
'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"), 'profile_pict': _("Profile: you need to be visible on the picture, in order to be recognized (e.g. by the barmen)"),
'avatar_pict': _("Avatar: used on the forum"), 'avatar_pict': _("Avatar: used on the forum"),
'scrub_pict': _("Scrub: let other know how your scrub looks like!"), 'scrub_pict': _("Scrub: let other know how your scrub looks like!"),
} }
def __init__(self, *arg, **kwargs): def __init__(self, *arg, **kwargs):
super(UserProfileForm, self).__init__(*arg, **kwargs) super(UserProfileForm, self).__init__(*arg, **kwargs)
@ -197,14 +206,14 @@ class UserProfileForm(forms.ModelForm):
self.cleaned_data['profile_pict'] = profile self.cleaned_data['profile_pict'] = profile
self.cleaned_data['scrub_pict'] = scrub self.cleaned_data['scrub_pict'] = scrub
parent = SithFile.objects.filter(parent=None, name="profiles").first() parent = SithFile.objects.filter(parent=None, name="profiles").first()
for field,f in files: for field, f in files:
with transaction.atomic(): with transaction.atomic():
try: try:
im = Image.open(BytesIO(f.read())) im = Image.open(BytesIO(f.read()))
new_file = SithFile(parent=parent, name=self.generate_name(field, f), new_file = SithFile(parent=parent, name=self.generate_name(field, f),
file=resize_image(im, 400, f.content_type.split('/')[-1]), file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size, owner=self.instance, is_folder=False, mime_type=f.content_type, size=f._size,
moderator=self.instance, is_moderated=True) moderator=self.instance, is_moderated=True)
new_file.file.name = new_file.name new_file.file.name = new_file.name
old = SithFile.objects.filter(parent=parent, name=new_file.name).first() old = SithFile.objects.filter(parent=parent, name=new_file.name).first()
if old: if old:
@ -216,16 +225,18 @@ class UserProfileForm(forms.ModelForm):
except ValidationError as e: except ValidationError as e:
self._errors.pop(field, None) self._errors.pop(field, None)
self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") %
{'file_name': f, 'msg': str(e.message)}) {'file_name': f, 'msg': str(e.message)})
except IOError: except IOError:
self._errors.pop(field, None) self._errors.pop(field, None)
self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") % self.add_error(field, _("Error uploading file %(file_name)s: %(msg)s") %
{'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")}) {'file_name': f, 'msg': _("Bad image format, only jpeg, png, and gif are accepted")})
self._post_clean() self._post_clean()
class UserPropForm(forms.ModelForm): class UserPropForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = User model = User
fields = ['groups'] fields = ['groups']
@ -236,13 +247,16 @@ class UserPropForm(forms.ModelForm):
'groups': CheckboxSelectMultiple, 'groups': CheckboxSelectMultiple,
} }
class UserGodfathersForm(forms.Form): class UserGodfathersForm(forms.Form):
type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add")) type = forms.ChoiceField(choices=[('godfather', _("Godfather")), ('godchild', _("Godchild"))], label=_("Add"))
user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None) user = AutoCompleteSelectField('users', required=True, label=_("Select user"), help_text=None)
class PagePropForm(forms.ModelForm): class PagePropForm(forms.ModelForm):
error_css_class = 'error' error_css_class = 'error'
required_css_class = 'required' required_css_class = 'required'
class Meta: class Meta:
model = Page model = Page
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ] fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ]
@ -255,4 +269,3 @@ class PagePropForm(forms.ModelForm):
super(PagePropForm, self).__init__(*arg, **kwargs) super(PagePropForm, self).__init__(*arg, **kwargs)
self.fields['edit_groups'].required = False self.fields['edit_groups'].required = False
self.fields['view_groups'].required = False self.fields['view_groups'].required = False

View File

@ -29,6 +29,7 @@ from django.core.urlresolvers import reverse_lazy
from core.models import RealGroup from core.models import RealGroup
from core.views import CanEditMixin from core.views import CanEditMixin
class GroupListView(CanEditMixin, ListView): class GroupListView(CanEditMixin, ListView):
""" """
Displays the group list Displays the group list
@ -36,17 +37,20 @@ class GroupListView(CanEditMixin, ListView):
model = RealGroup model = RealGroup
template_name = "core/group_list.jinja" template_name = "core/group_list.jinja"
class GroupEditView(CanEditMixin, UpdateView): class GroupEditView(CanEditMixin, UpdateView):
model = RealGroup model = RealGroup
pk_url_kwarg = "group_id" pk_url_kwarg = "group_id"
template_name = "core/group_edit.jinja" template_name = "core/group_edit.jinja"
fields = ['name', 'description'] fields = ['name', 'description']
class GroupCreateView(CanEditMixin, CreateView): class GroupCreateView(CanEditMixin, CreateView):
model = RealGroup model = RealGroup
template_name = "core/group_edit.jinja" template_name = "core/group_edit.jinja"
fields = ['name', 'description'] fields = ['name', 'description']
class GroupDeleteView(CanEditMixin, DeleteView): class GroupDeleteView(CanEditMixin, DeleteView):
model = RealGroup model = RealGroup
pk_url_kwarg = "group_id" pk_url_kwarg = "group_id"

View File

@ -23,23 +23,22 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.shortcuts import render, redirect, get_object_or_404
from django.core.urlresolvers import reverse_lazy from django.core.urlresolvers import reverse_lazy
from django.views.generic import ListView, DetailView from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.contrib.auth.decorators import login_required, permission_required
from django.utils.decorators import method_decorator
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple, modelform_factory from django.forms import CheckboxSelectMultiple
from core.models import Page, PageRev, LockError from core.models import Page, PageRev, LockError
from core.views.forms import PagePropForm, MarkdownInput from core.views.forms import MarkdownInput
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
class PageListView(CanViewMixin, ListView): class PageListView(CanViewMixin, ListView):
model = Page model = Page
template_name = 'core/page_list.jinja' template_name = 'core/page_list.jinja'
class PageView(CanViewMixin, DetailView): class PageView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_detail.jinja' template_name = 'core/page_detail.jinja'
@ -54,6 +53,7 @@ class PageView(CanViewMixin, DetailView):
context['new_page'] = self.kwargs['page_name'] context['new_page'] = self.kwargs['page_name']
return context return context
class PageHistView(CanViewMixin, DetailView): class PageHistView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_hist.jinja' template_name = 'core/page_hist.jinja'
@ -62,6 +62,7 @@ class PageHistView(CanViewMixin, DetailView):
self.page = Page.get_page_by_full_name(self.kwargs['page_name']) self.page = Page.get_page_by_full_name(self.kwargs['page_name'])
return self.page return self.page
class PageRevView(CanViewMixin, DetailView): class PageRevView(CanViewMixin, DetailView):
model = Page model = Page
template_name = 'core/page_detail.jinja' template_name = 'core/page_detail.jinja'
@ -78,20 +79,21 @@ class PageRevView(CanViewMixin, DetailView):
rev = self.page.revisions.get(id=self.kwargs['rev']) rev = self.page.revisions.get(id=self.kwargs['rev'])
context['rev'] = rev context['rev'] = rev
except: except:
# By passing, the template will just display the normal page without taking revision into account # By passing, the template will just display the normal page without taking revision into account
pass pass
else: else:
context['new_page'] = self.kwargs['page_name'] context['new_page'] = self.kwargs['page_name']
return context return context
class PageCreateView(CanCreateMixin, CreateView): class PageCreateView(CanCreateMixin, CreateView):
model = Page model = Page
form_class = modelform_factory(Page, form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ], fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={ widgets={
'edit_groups':CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
}) })
template_name = 'core/page_prop.jinja' template_name = 'core/page_prop.jinja'
def get_initial(self): def get_initial(self):
@ -115,14 +117,15 @@ class PageCreateView(CanCreateMixin, CreateView):
ret = super(PageCreateView, self).form_valid(form) ret = super(PageCreateView, self).form_valid(form)
return ret return ret
class PagePropView(CanEditPropMixin, UpdateView): class PagePropView(CanEditPropMixin, UpdateView):
model = Page model = Page
form_class = modelform_factory(Page, form_class = modelform_factory(Page,
fields = ['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ], fields=['parent', 'name', 'owner_group', 'edit_groups', 'view_groups', ],
widgets={ widgets={
'edit_groups':CheckboxSelectMultiple, 'edit_groups': CheckboxSelectMultiple,
'view_groups':CheckboxSelectMultiple, 'view_groups': CheckboxSelectMultiple,
}) })
template_name = 'core/page_prop.jinja' template_name = 'core/page_prop.jinja'
slug_field = '_full_name' slug_field = '_full_name'
slug_url_kwarg = 'page_name' slug_url_kwarg = 'page_name'
@ -130,7 +133,7 @@ class PagePropView(CanEditPropMixin, UpdateView):
def get_object(self): def get_object(self):
o = super(PagePropView, self).get_object() o = super(PagePropView, self).get_object()
# Create the page if it does not exists # Create the page if it does not exists
#if p == None: # if p == None:
# parent_name = '/'.join(page_name.split('/')[:-1]) # parent_name = '/'.join(page_name.split('/')[:-1])
# name = page_name.split('/')[-1] # name = page_name.split('/')[-1]
# if parent_name == "": # if parent_name == "":
@ -145,9 +148,10 @@ class PagePropView(CanEditPropMixin, UpdateView):
raise e raise e
return self.page return self.page
class PageEditView(CanEditMixin, UpdateView): class PageEditView(CanEditMixin, UpdateView):
model = PageRev model = PageRev
form_class = modelform_factory(model=PageRev, fields=['title', 'content',], widgets={'content': MarkdownInput}) form_class = modelform_factory(model=PageRev, fields=['title', 'content', ], widgets={'content': MarkdownInput})
template_name = 'core/pagerev_edit.jinja' template_name = 'core/pagerev_edit.jinja'
def get_object(self): def get_object(self):

View File

@ -22,17 +22,13 @@
# #
# #
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect
from django.db import models
from django.http import JsonResponse from django.http import JsonResponse
from django.core import serializers from django.core import serializers
from django.db.models import Q
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.views.generic import ListView, TemplateView from django.views.generic import ListView, TemplateView
import os
import json import json
from itertools import chain
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
@ -40,9 +36,11 @@ from core.models import User, Notification
from core.utils import doku_to_markdown, bbcode_to_markdown from core.utils import doku_to_markdown, bbcode_to_markdown
from club.models import Club from club.models import Club
def index(request, context=None): def index(request, context=None):
return render(request, "core/index.jinja") return render(request, "core/index.jinja")
class NotificationList(ListView): class NotificationList(ListView):
model = Notification model = Notification
template_name = "core/notification_list.jinja" template_name = "core/notification_list.jinja"
@ -52,6 +50,7 @@ class NotificationList(ListView):
self.request.user.notifications.update(viewed=True) self.request.user.notifications.update(viewed=True)
return self.request.user.notifications.order_by('-id')[:20] return self.request.user.notifications.order_by('-id')[:20]
def notification(request, notif_id): def notification(request, notif_id):
notif = Notification.objects.filter(id=notif_id).first() notif = Notification.objects.filter(id=notif_id).first()
if notif: if notif:
@ -60,44 +59,50 @@ def notification(request, notif_id):
return redirect(notif.url) return redirect(notif.url)
return redirect("/") return redirect("/")
def search_user(query, as_json=False): def search_user(query, as_json=False):
res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20] res = SearchQuerySet().models(User).filter(text=query).filter_or(text__contains=query)[:20]
return [r.object for r in res] return [r.object for r in res]
def search_club(query, as_json=False): def search_club(query, as_json=False):
clubs = [] clubs = []
if query: if query:
clubs = Club.objects.filter(name__icontains=query).all() clubs = Club.objects.filter(name__icontains=query).all()
clubs = clubs[:5] clubs = clubs[:5]
if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers if as_json: # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
clubs = json.loads(serializers.serialize('json', clubs, fields=('name'))) clubs = json.loads(serializers.serialize('json', clubs, fields=('name')))
else: else:
clubs = list(clubs) clubs = list(clubs)
return clubs return clubs
@login_required @login_required
def search_view(request): def search_view(request):
result = { result = {
'users': search_user(request.GET.get('query', '')), 'users': search_user(request.GET.get('query', '')),
'clubs': search_club(request.GET.get('query', '')), 'clubs': search_club(request.GET.get('query', '')),
} }
return render(request, "core/search.jinja", context={'result': result}) return render(request, "core/search.jinja", context={'result': result})
@login_required @login_required
def search_user_json(request): def search_user_json(request):
result = { result = {
'users': search_user(request.GET.get('query', ''), True), 'users': search_user(request.GET.get('query', ''), True),
} }
return JsonResponse(result) return JsonResponse(result)
@login_required @login_required
def search_json(request): def search_json(request):
result = { result = {
'users': search_user(request.GET.get('query', ''), True), 'users': search_user(request.GET.get('query', ''), True),
'clubs': search_club(request.GET.get('query', ''), True), 'clubs': search_club(request.GET.get('query', ''), True),
} }
return JsonResponse(result) return JsonResponse(result)
class ToMarkdownView(TemplateView): class ToMarkdownView(TemplateView):
template_name = "core/to_markdown.jinja" template_name = "core/to_markdown.jinja"
@ -119,4 +124,3 @@ class ToMarkdownView(TemplateView):
kwargs['text'] = "" kwargs['text'] = ""
kwargs['text_md'] = "" kwargs['text_md'] = ""
return kwargs return kwargs

View File

@ -24,30 +24,29 @@
# This file contains all the views that concern the user model # This file contains all the views that concern the user model
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth import logout as auth_logout, views from django.contrib.auth import views
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import PermissionDenied, ObjectDoesNotExist, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.http import Http404 from django.http import Http404
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from django.views.generic import ListView, DetailView, TemplateView, DeleteView from django.views.generic import ListView, DetailView, TemplateView
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.conf import settings from django.conf import settings
from django.views.generic.dates import YearMixin, MonthMixin from django.views.generic.dates import YearMixin, MonthMixin
from django.utils import timezone from datetime import timedelta, date
from datetime import timedelta, datetime, date
import logging import logging
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, TabedViewMixin, QuickNotifMixin
from core.views.forms import RegisteringForm, UserPropForm, UserProfileForm, LoginForm, UserGodfathersForm from core.views.forms import RegisteringForm, UserProfileForm, LoginForm, UserGodfathersForm
from core.models import User, SithFile, Preferences from core.models import User, SithFile, Preferences
from club.models import Club
from subscription.models import Subscription from subscription.models import Subscription
from trombi.views import UserTrombiForm from trombi.views import UserTrombiForm
def login(request): def login(request):
""" """
The login view The login view
@ -56,24 +55,28 @@ def login(request):
""" """
return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm) return views.login(request, template_name="core/login.jinja", authentication_form=LoginForm)
def logout(request): def logout(request):
""" """
The logout view The logout view
""" """
return views.logout_then_login(request) return views.logout_then_login(request)
def password_change(request): def password_change(request):
""" """
Allows a user to change its password Allows a user to change its password
""" """
return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done")) return views.password_change(request, template_name="core/password_change.jinja", post_change_redirect=reverse("core:password_change_done"))
def password_change_done(request): def password_change_done(request):
""" """
Allows a user to change its password Allows a user to change its password
""" """
return views.password_change_done(request, template_name="core/password_change_done.jinja") return views.password_change_done(request, template_name="core/password_change_done.jinja")
def password_root_change(request, user_id): def password_root_change(request, user_id):
""" """
Allows a root user to change someone's password Allows a root user to change someone's password
@ -92,6 +95,7 @@ def password_root_change(request, user_id):
form = views.SetPasswordForm(user=user) form = views.SetPasswordForm(user=user)
return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user}) return TemplateResponse(request, "core/password_change.jinja", {'form': form, 'target': user})
def password_reset(request): def password_reset(request):
""" """
Allows someone to enter an email adresse for resetting password Allows someone to enter an email adresse for resetting password
@ -100,7 +104,8 @@ def password_reset(request):
template_name="core/password_reset.jinja", template_name="core/password_reset.jinja",
email_template_name="core/password_reset_email.jinja", email_template_name="core/password_reset_email.jinja",
post_reset_redirect="core:password_reset_done", post_reset_redirect="core:password_reset_done",
) )
def password_reset_done(request): def password_reset_done(request):
""" """
@ -108,6 +113,7 @@ def password_reset_done(request):
""" """
return views.password_reset_done(request, template_name="core/password_reset_done.jinja") return views.password_reset_done(request, template_name="core/password_reset_done.jinja")
def password_reset_confirm(request, uidb64=None, token=None): def password_reset_confirm(request, uidb64=None, token=None):
""" """
Provide a reset password formular Provide a reset password formular
@ -115,7 +121,8 @@ def password_reset_confirm(request, uidb64=None, token=None):
return views.password_reset_confirm(request, uidb64=uidb64, token=token, return views.password_reset_confirm(request, uidb64=uidb64, token=token,
post_reset_redirect="core:password_reset_complete", post_reset_redirect="core:password_reset_complete",
template_name="core/password_reset_confirm.jinja", template_name="core/password_reset_confirm.jinja",
) )
def password_reset_complete(request): def password_reset_complete(request):
""" """
@ -123,14 +130,15 @@ def password_reset_complete(request):
""" """
return views.password_reset_complete(request, return views.password_reset_complete(request,
template_name="core/password_reset_complete.jinja", template_name="core/password_reset_complete.jinja",
) )
def register(request): def register(request):
context = {} context = {}
if request.method == 'POST': if request.method == 'POST':
form = RegisteringForm(request.POST) form = RegisteringForm(request.POST)
if form.is_valid(): if form.is_valid():
logging.debug("Registering "+form.cleaned_data['first_name']+form.cleaned_data['last_name']) logging.debug("Registering " + form.cleaned_data['first_name'] + form.cleaned_data['last_name'])
u = form.save() u = form.save()
context['user_registered'] = u context['user_registered'] = u
context['tests'] = 'TEST_REGISTER_USER_FORM_OK' context['tests'] = 'TEST_REGISTER_USER_FORM_OK'
@ -143,6 +151,7 @@ def register(request):
context['form'] = form.as_p() context['form'] = form.as_p()
return render(request, "core/register.jinja", context) return render(request, "core/register.jinja", context)
class UserTabsMixin(TabedViewMixin): class UserTabsMixin(TabedViewMixin):
def get_tabs_title(self): def get_tabs_title(self):
return self.object.get_display_name() return self.object.get_display_name()
@ -150,67 +159,69 @@ class UserTabsMixin(TabedViewMixin):
def get_list_of_tabs(self): def get_list_of_tabs(self):
tab_list = [] tab_list = []
tab_list.append({ tab_list.append({
'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_profile', kwargs={'user_id': self.object.id}),
'slug': 'infos', 'slug': 'infos',
'name': _("Infos"), 'name': _("Infos"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_godfathers', kwargs={'user_id': self.object.id}),
'slug': 'godfathers', 'slug': 'godfathers',
'name': _("Godfathers"), 'name': _("Godfathers"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_pictures', kwargs={'user_id': self.object.id}),
'slug': 'pictures', 'slug': 'pictures',
'name': _("Pictures"), 'name': _("Pictures"),
}) })
if self.request.user == self.object: if self.request.user == self.object:
tab_list.append({ tab_list.append({
'url': reverse('core:user_tools'), 'url': reverse('core:user_tools'),
'slug': 'tools', 'slug': 'tools',
'name': _("Tools"), 'name': _("Tools"),
}) })
if self.request.user.can_edit(self.object): if self.request.user.can_edit(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_edit', kwargs={'user_id': self.object.id}),
'slug': 'edit', 'slug': 'edit',
'name': _("Edit"), 'name': _("Edit"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_prefs', kwargs={'user_id': self.object.id}),
'slug': 'prefs', 'slug': 'prefs',
'name': _("Preferences"), 'name': _("Preferences"),
}) })
if self.request.user.can_view(self.object): if self.request.user.can_view(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_clubs', kwargs={'user_id': self.object.id}),
'slug': 'clubs', 'slug': 'clubs',
'name': _("Clubs"), 'name': _("Clubs"),
}) })
if self.request.user.is_owner(self.object): if self.request.user.is_owner(self.object):
tab_list.append({ tab_list.append({
'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_groups', kwargs={'user_id': self.object.id}),
'slug': 'groups', 'slug': 'groups',
'name': _("Groups"), 'name': _("Groups"),
}) })
try: try:
if (self.object.customer and (self.object == self.request.user if (self.object.customer and (self.object == self.request.user
or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or self.request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or self.request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or self.request.user.is_root)): or self.request.user.is_root)):
tab_list.append({ tab_list.append({
'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_stats', kwargs={'user_id': self.object.id}),
'slug': 'stats', 'slug': 'stats',
'name': _("Stats"), 'name': _("Stats"),
}) })
tab_list.append({ tab_list.append({
'url': reverse('core:user_account', kwargs={'user_id': self.object.id}), 'url': reverse('core:user_account', kwargs={'user_id': self.object.id}),
'slug': 'account', 'slug': 'account',
'name': _("Account")+" (%s €)" % self.object.customer.amount, 'name': _("Account") + " (%s €)" % self.object.customer.amount,
}) })
except: pass except:
pass
return tab_list return tab_list
class UserView(UserTabsMixin, CanViewMixin, DetailView): class UserView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's profile Display a user's profile
@ -225,8 +236,8 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView):
def DeleteUserGodfathers(request, user_id, godfather_id, is_father): def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
if ((user == request.user) or if ((user == request.user) or
request.user.is_root or request.user.is_root or
request.user.is_board_member): request.user.is_board_member):
ud = get_object_or_404(User, id=godfather_id) ud = get_object_or_404(User, id=godfather_id)
if is_father == "True": if is_father == "True":
user.godfathers.remove(ud) user.godfathers.remove(ud)
@ -236,6 +247,7 @@ def DeleteUserGodfathers(request, user_id, godfather_id, is_father):
raise PermissionDenied raise PermissionDenied
return redirect('core:user_godfathers', user_id=user_id) return redirect('core:user_godfathers', user_id=user_id)
class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView): class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's pictures Display a user's pictures
@ -246,6 +258,7 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_pictures.jinja" template_name = "core/user_pictures.jinja"
current_tab = 'pictures' current_tab = 'pictures'
class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView): class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's godfathers Display a user's godfathers
@ -277,6 +290,7 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
kwargs['form'] = UserGodfathersForm() kwargs['form'] = UserGodfathersForm()
return kwargs return kwargs
class UserStatsView(UserTabsMixin, CanViewMixin, DetailView): class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display a user's stats Display a user's stats
@ -295,7 +309,7 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
if not (profile == request.user if not (profile == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or request.user.is_root): or request.user.is_root):
raise PermissionDenied raise PermissionDenied
@ -303,26 +317,27 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(UserStatsView, self).get_context_data(**kwargs) kwargs = super(UserStatsView, self).get_context_data(**kwargs)
from counter.models import Counter, Product, Selling from counter.models import Counter
from django.db.models import Sum from django.db.models import Sum
foyer = Counter.objects.filter(name="Foyer").first() foyer = Counter.objects.filter(name="Foyer").first()
mde = Counter.objects.filter(name="MDE").first() mde = Counter.objects.filter(name="MDE").first()
gommette = Counter.objects.filter(name="La Gommette").first() gommette = Counter.objects.filter(name="La Gommette").first()
semester_start=Subscription.compute_start(d=date.today(), duration=3) semester_start = Subscription.compute_start(d=date.today(), duration=3)
kwargs['total_perm_time'] = sum([p.end-p.start for p in self.object.permanencies.exclude(end=None)], timedelta()) kwargs['total_perm_time'] = sum([p.end - p.start for p in self.object.permanencies.exclude(end=None)], timedelta())
kwargs['total_foyer_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta()) kwargs['total_foyer_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=foyer).exclude(end=None)], timedelta())
kwargs['total_mde_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta()) kwargs['total_mde_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=mde).exclude(end=None)], timedelta())
kwargs['total_gommette_time'] = sum([p.end-p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta()) kwargs['total_gommette_time'] = sum([p.end - p.start for p in self.object.permanencies.filter(counter=gommette).exclude(end=None)], timedelta())
kwargs['total_foyer_buyings'] = sum([b.unit_price*b.quantity for b in kwargs['total_foyer_buyings'] = sum([b.unit_price * b.quantity for b in
self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)]) self.object.customer.buyings.filter(counter=foyer, date__gte=semester_start)])
kwargs['total_mde_buyings'] = sum([b.unit_price*b.quantity for b in self.object.customer.buyings.filter(counter=mde, kwargs['total_mde_buyings'] = sum([b.unit_price * b.quantity for b in self.object.customer.buyings.filter(counter=mde,
date__gte=semester_start)]) date__gte=semester_start)])
kwargs['total_gommette_buyings'] = sum([b.unit_price*b.quantity for b in kwargs['total_gommette_buyings'] = sum([b.unit_price * b.quantity for b in
self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)]) self.object.customer.buyings.filter(counter=gommette, date__gte=semester_start)])
kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate( kwargs['top_product'] = self.object.customer.buyings.values('product__name').annotate(
product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10] product_sum=Sum('quantity')).exclude(product_sum=None).order_by('-product_sum').all()[:10]
return kwargs return kwargs
class UserMiniView(CanViewMixin, DetailView): class UserMiniView(CanViewMixin, DetailView):
""" """
Display a user's profile Display a user's profile
@ -332,6 +347,7 @@ class UserMiniView(CanViewMixin, DetailView):
context_object_name = "profile" context_object_name = "profile"
template_name = "core/user_mini.jinja" template_name = "core/user_mini.jinja"
class UserListView(ListView, CanEditPropMixin): class UserListView(ListView, CanEditPropMixin):
""" """
Displays the user list Displays the user list
@ -339,6 +355,7 @@ class UserListView(ListView, CanEditPropMixin):
model = User model = User
template_name = "core/user_list.jinja" template_name = "core/user_list.jinja"
class UserUploadProfilePictView(CanEditMixin, DetailView): class UserUploadProfilePictView(CanEditMixin, DetailView):
""" """
Handle the upload of the profile picture taken with webcam in navigator Handle the upload of the profile picture taken with webcam in navigator
@ -356,17 +373,18 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
raise ValidationError(_("User already has a profile picture")) raise ValidationError(_("User already has a profile picture"))
f = request.FILES['new_profile_pict'] f = request.FILES['new_profile_pict']
parent = SithFile.objects.filter(parent=None, name="profiles").first() parent = SithFile.objects.filter(parent=None, name="profiles").first()
name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs name = str(self.object.id) + "_profile.jpg" # Webcamejs uploads JPGs
im = Image.open(BytesIO(f.read())) im = Image.open(BytesIO(f.read()))
new_file = SithFile(parent=parent, name=name, new_file = SithFile(parent=parent, name=name,
file=resize_image(im, 400, f.content_type.split('/')[-1]), file=resize_image(im, 400, f.content_type.split('/')[-1]),
owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size) owner=self.object, is_folder=False, mime_type=f.content_type, size=f._size)
new_file.file.name = name new_file.file.name = name
new_file.save() new_file.save()
self.object.profile_pict = new_file self.object.profile_pict = new_file
self.object.save() self.object.save()
return redirect("core:user_edit", user_id=self.object.id) return redirect("core:user_edit", user_id=self.object.id)
class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView): class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
""" """
Edit a user's profile Edit a user's profile
@ -412,6 +430,7 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['form'] = self.form kwargs['form'] = self.form
return kwargs return kwargs
class UserClubView(UserTabsMixin, CanViewMixin, DetailView): class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
""" """
Display the user's club(s) Display the user's club(s)
@ -422,6 +441,7 @@ class UserClubView(UserTabsMixin, CanViewMixin, DetailView):
template_name = "core/user_clubs.jinja" template_name = "core/user_clubs.jinja"
current_tab = "clubs" current_tab = "clubs"
class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView): class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
""" """
Edit a user's preferences Edit a user's preferences
@ -453,6 +473,7 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
kwargs['trombi_form'] = UserTrombiForm() kwargs['trombi_form'] = UserTrombiForm()
return kwargs return kwargs
class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView): class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
""" """
Edit a user's groups Edit a user's groups
@ -461,10 +482,11 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
pk_url_kwarg = "user_id" pk_url_kwarg = "user_id"
template_name = "core/user_group.jinja" template_name = "core/user_group.jinja"
form_class = modelform_factory(User, fields=['groups'], form_class = modelform_factory(User, fields=['groups'],
widgets={'groups':CheckboxSelectMultiple}) widgets={'groups': CheckboxSelectMultiple})
context_object_name = "profile" context_object_name = "profile"
current_tab = "groups" current_tab = "groups"
class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView): class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
""" """
Displays the logged user's tools Displays the logged user's tools
@ -481,6 +503,7 @@ class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
kwargs['object'] = self.request.user kwargs['object'] = self.request.user
return kwargs return kwargs
class UserAccountBase(UserTabsMixin, DetailView): class UserAccountBase(UserTabsMixin, DetailView):
""" """
Base class for UserAccount Base class for UserAccount
@ -489,15 +512,16 @@ class UserAccountBase(UserTabsMixin, DetailView):
pk_url_kwarg = "user_id" pk_url_kwarg = "user_id"
current_tab = "account" current_tab = "account"
def dispatch(self, request, *arg, **kwargs): # Manually validates the rights def dispatch(self, request, *arg, **kwargs): # Manually validates the rights
res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs) res = super(UserAccountBase, self).dispatch(request, *arg, **kwargs)
if (self.object == request.user if (self.object == request.user
or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID) or request.user.is_in_group(settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name']+settings.SITH_BOARD_SUFFIX) or request.user.is_in_group(settings.SITH_BAR_MANAGER['unix_name'] + settings.SITH_BOARD_SUFFIX)
or request.user.is_root): or request.user.is_root):
return res return res
raise PermissionDenied raise PermissionDenied
class UserAccountView(UserAccountBase): class UserAccountView(UserAccountBase):
""" """
Display a user's account Display a user's account
@ -511,14 +535,14 @@ class UserAccountView(UserAccountBase):
stats.append([]) stats.append([])
i = 0 i = 0
for month in obj.filter(date__year=year.year).datetimes( for month in obj.filter(date__year=year.year).datetimes(
'date', 'month', order='DESC'): 'date', 'month', order='DESC'):
q = obj.filter( q = obj.filter(
date__year=month.year, date__year=month.year,
date__month=month.month date__month=month.month
) )
stats[i].append({ stats[i].append({
'sum':sum([calc(p) for p in q]), 'sum': sum([calc(p) for p in q]),
'date':month 'date': month
}) })
i += 1 i += 1
return stats return stats
@ -551,6 +575,7 @@ class UserAccountView(UserAccountBase):
print(repr(e)) print(repr(e))
return kwargs return kwargs
class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin): class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
""" """
Display a user's account for month Display a user's account for month
@ -568,4 +593,3 @@ class UserAccountDetailView(UserAccountBase, YearMixin, MonthMixin):
pass pass
kwargs['tab'] = "account" kwargs['tab'] = "account"
return kwargs return kwargs