Compare commits
1 commit
master
...
aureplop/s
Author | SHA1 | Date | |
---|---|---|---|
|
82d361e775 |
189 changed files with 9189 additions and 9755 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,8 +1,7 @@
|
|||
.vagrant/
|
||||
__pycache__
|
||||
venv
|
||||
poulpe/settings.py
|
||||
evenementiel/settings.py
|
||||
.*.swp
|
||||
*.pyc
|
||||
*.sqlite3
|
||||
*.scssc
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'api.apps.APIConfig'
|
|
@ -1,7 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class APIConfig(AppConfig):
|
||||
name = 'api'
|
||||
verbose_name = _("API")
|
|
@ -1,9 +1,6 @@
|
|||
from django.db import transaction
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
from event.models import Event, ActivityTag, Place, ActivityTemplate
|
||||
from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
|
||||
|
||||
from .fields import EventHyperlinkedIdentityField
|
||||
|
||||
|
@ -40,19 +37,11 @@ class ActivityTagSerializer(serializers.ModelSerializer):
|
|||
fields = ('url', 'id', 'name', 'is_public', 'color', 'event')
|
||||
|
||||
|
||||
# TODO rajouter des permissions
|
||||
class ActivityTemplateSerializer(serializers.ModelSerializer):
|
||||
class BaseActivitySerializer(serializers.ModelSerializer):
|
||||
tags = ActivityTagSerializer(many=True)
|
||||
|
||||
serializer_url_field = EventHyperlinkedIdentityField
|
||||
|
||||
class Meta:
|
||||
model = ActivityTemplate
|
||||
fields = (
|
||||
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
|
||||
'max_perm', 'description', 'remarks', 'tags',
|
||||
)
|
||||
|
||||
def process_tags(self, instance, tags_data):
|
||||
# TODO: en fonction de si backbone envoie un `id` ou non lorsque le tag
|
||||
# n'existe pas encore il faudra faire un premier passage sur `tags` i
|
||||
|
@ -88,5 +77,29 @@ class ActivityTemplateSerializer(serializers.ModelSerializer):
|
|||
return activity_template
|
||||
|
||||
|
||||
class ActivitySerializer(serializers.ModelSerializer):
|
||||
pass
|
||||
# TODO rajouter des permissions
|
||||
class ActivityTemplateSerializer(
|
||||
BaseActivitySerializer,
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
model = ActivityTemplate
|
||||
fields = (
|
||||
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
|
||||
'max_perm', 'description', 'remarks', 'tags', 'places',
|
||||
)
|
||||
|
||||
|
||||
class ActivitySerializer(
|
||||
BaseActivitySerializer,
|
||||
serializers.ModelSerializer,
|
||||
):
|
||||
|
||||
class Meta:
|
||||
model = Activity
|
||||
fields = (
|
||||
'url', 'id', 'title', 'event', 'is_public', 'has_perm', 'min_perm',
|
||||
'max_perm', 'description', 'remarks', 'tags', 'places',
|
||||
'parent', 'staff', 'beginning', 'end',
|
||||
)
|
||||
|
|
|
@ -5,7 +5,7 @@ from django.utils import timezone
|
|||
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from event.models import Event, Place, ActivityTag, ActivityTemplate
|
||||
from event.models import Activity, ActivityTag, ActivityTemplate, Event, Place
|
||||
|
||||
from api.test.testcases import ModelAPITestCaseMixin
|
||||
from api.test.utils import json_format
|
||||
|
@ -134,6 +134,11 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
|
|||
'color': "#222",
|
||||
})
|
||||
|
||||
self.place = Place.objects.create(
|
||||
name='The Place to Be',
|
||||
description='Super great',
|
||||
)
|
||||
|
||||
def get_url_model(self, *args, **kwargs):
|
||||
kwargs['event_pk'] = 1
|
||||
return super().get_url_model(*args, **kwargs)
|
||||
|
@ -180,6 +185,7 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
|
|||
'min_perm': instance.min_perm,
|
||||
'max_perm': instance.max_perm,
|
||||
'description': instance.description,
|
||||
'places': [place.pk for place in instance.places.all()],
|
||||
})
|
||||
|
||||
@property
|
||||
|
@ -196,7 +202,8 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
|
|||
'is_public': False,
|
||||
'remarks': "test remark",
|
||||
'event': self.event,
|
||||
'tags': [self.activity_tag]
|
||||
'tags': [self.activity_tag],
|
||||
'places': [self.place],
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -240,6 +247,7 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
|
|||
'color': '#555',
|
||||
},
|
||||
],
|
||||
'places': [str(self.place.pk)],
|
||||
}
|
||||
|
||||
@property
|
||||
|
@ -254,7 +262,192 @@ class ActivityTemplateAPITests(ModelAPITestCaseMixin, APITestCase):
|
|||
'title': "act temp3",
|
||||
'is_public': False,
|
||||
'remarks': "another test remark",
|
||||
'tags': [tag_root, tag_bound]
|
||||
'tags': [tag_root, tag_bound],
|
||||
'places': [self.place],
|
||||
}
|
||||
|
||||
|
||||
class ActivityAPITests(ModelAPITestCaseMixin, APITestCase):
|
||||
model = Activity
|
||||
url_label = 'activity'
|
||||
|
||||
list_ordering = 'title'
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.event = Event.objects.create(
|
||||
title='Event',
|
||||
slug='the-slug',
|
||||
beginning_date=self.now + timedelta(days=10),
|
||||
ending_date=self.now + timedelta(days=20),
|
||||
description="La Nuit n'aura pas lieu.",
|
||||
created_by=self.user,
|
||||
)
|
||||
|
||||
self.activity_tag = ActivityTag.objects.create(
|
||||
name='tag2',
|
||||
is_public=False,
|
||||
color='#222',
|
||||
)
|
||||
|
||||
self.place = Place.objects.create(
|
||||
name='The Place to Be',
|
||||
description='Super great',
|
||||
)
|
||||
|
||||
self.activity_template = ActivityTemplate.objects.create(
|
||||
title=None,
|
||||
event=self.event,
|
||||
is_public=None,
|
||||
has_perm=True,
|
||||
min_perm=2,
|
||||
max_perm=4,
|
||||
description="Il faut tenir le quai.",
|
||||
remarks="",
|
||||
)
|
||||
self.activity_template.tags.set([self.activity_tag])
|
||||
self.activity_template.places.set([self.place])
|
||||
|
||||
def get_url_model(self, *args, **kwargs):
|
||||
kwargs['event_pk'] = self.event.pk
|
||||
return super().get_url_model(*args, **kwargs)
|
||||
|
||||
def get_url_object(self, *args, **kwargs):
|
||||
kwargs['event_pk'] = self.event.pk
|
||||
return super().get_url_object(*args, **kwargs)
|
||||
|
||||
def get_expected_data(self, instance):
|
||||
return json_format({
|
||||
'url': (
|
||||
'http://testserver/api/event/{event_pk}/activity/{pk}/'
|
||||
.format(event_pk=instance.event.pk, pk=instance.pk)
|
||||
),
|
||||
'id': instance.id,
|
||||
'title': instance.title,
|
||||
'is_public': instance.is_public,
|
||||
'remarks': instance.remarks,
|
||||
'event': instance.event.pk,
|
||||
'tags': [
|
||||
{
|
||||
**(tag.event and {
|
||||
'url': (
|
||||
'http://testserver/api/event/{event_pk}/'
|
||||
'tag/{tag_pk}/'
|
||||
.format(event_pk=tag.event.pk, tag_pk=tag.pk)
|
||||
),
|
||||
'event': tag.event.pk,
|
||||
} or {
|
||||
'url': (
|
||||
'http://testserver/api/tag/{tag_pk}/'
|
||||
.format(tag_pk=tag.pk)
|
||||
),
|
||||
'event': None,
|
||||
}),
|
||||
'id': tag.id,
|
||||
'name': tag.name,
|
||||
'is_public': tag.is_public,
|
||||
'color': tag.color,
|
||||
}
|
||||
for tag in instance.tags.all()
|
||||
],
|
||||
'has_perm': instance.has_perm,
|
||||
'min_perm': instance.min_perm,
|
||||
'max_perm': instance.max_perm,
|
||||
'description': instance.description,
|
||||
'places': [place.pk for place in instance.places.all()],
|
||||
'parent': instance.parent.pk if instance.parent else None,
|
||||
'staff': [u.pk for u in instance.staff.all()],
|
||||
'beginning': instance.beginning,
|
||||
'end': instance.end,
|
||||
})
|
||||
|
||||
@property
|
||||
def instances_data(self):
|
||||
return [
|
||||
{
|
||||
'title': "act",
|
||||
'is_public': True,
|
||||
'remarks': "test remark",
|
||||
'event': self.event,
|
||||
'beginning': self.now + timedelta(days=12),
|
||||
'end': self.now + timedelta(days=13),
|
||||
},
|
||||
{
|
||||
'title': "act",
|
||||
'is_public': False,
|
||||
'remarks': "test remark",
|
||||
'event': self.event,
|
||||
'tags': [self.activity_tag],
|
||||
'places': [self.place],
|
||||
'beginning': self.now + timedelta(days=15),
|
||||
'end': self.now + timedelta(days=18),
|
||||
},
|
||||
]
|
||||
|
||||
@property
|
||||
def create_data(self):
|
||||
return {
|
||||
'title': "act",
|
||||
'is_public': False,
|
||||
'remarks': "test remark",
|
||||
'tags': [
|
||||
{
|
||||
'name': "tag2",
|
||||
'is_public': False,
|
||||
'color': '#222',
|
||||
},
|
||||
],
|
||||
'beginning': '2017-10-20 15:45:00',
|
||||
'end': '2017-10-21 04:30:00',
|
||||
}
|
||||
|
||||
@property
|
||||
def create_expected(self):
|
||||
return {
|
||||
**self.create_data,
|
||||
'tags': [ActivityTag.objects.get(name='tag2')],
|
||||
'beginning': timezone.make_aware(
|
||||
datetime(2017, 10, 20, 15, 45, 00)),
|
||||
'end': timezone.make_aware(
|
||||
datetime(2017, 10, 21, 4, 30, 00)),
|
||||
}
|
||||
|
||||
@property
|
||||
def update_data(self):
|
||||
return {
|
||||
'title': "act",
|
||||
'is_public': False,
|
||||
'remarks': "another test remark",
|
||||
'tags': [
|
||||
{
|
||||
'name': "tag2",
|
||||
'is_public': False,
|
||||
'color': '#222',
|
||||
},
|
||||
{
|
||||
'name': "body",
|
||||
'is_public': True,
|
||||
'color': '#555',
|
||||
},
|
||||
],
|
||||
'places': [str(self.place.pk)],
|
||||
}
|
||||
|
||||
@property
|
||||
def update_expected(self):
|
||||
tag_root = ActivityTag.objects.get(name='tag2')
|
||||
self.assertIsNone(tag_root.event)
|
||||
|
||||
tag_bound = ActivityTag.objects.get(name='body')
|
||||
self.assertEqual(tag_bound.event, self.event)
|
||||
|
||||
return {
|
||||
'title': "act",
|
||||
'is_public': False,
|
||||
'remarks': "another test remark",
|
||||
'tags': [tag_root, tag_bound],
|
||||
'places': [self.place],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ event_router = NestedSimpleRouter(router, r'event', lookup='event')
|
|||
event_router.register(r'place', views.PlaceViewSet)
|
||||
event_router.register(r'tag', views.ActivityTagViewSet)
|
||||
event_router.register(r'template', views.ActivityTemplateViewSet)
|
||||
event_router.register(r'activity', views.ActivityViewSet)
|
||||
|
||||
|
||||
# API URLconf: routers + auth for browsable API.
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'communication.apps.CommunicationConfig'
|
|
@ -1,7 +1,4 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class CommunicationConfig(AppConfig):
|
||||
name = 'communication'
|
||||
verbose_name = _("Communication")
|
||||
|
|
|
@ -20,10 +20,7 @@ class Subscription(models.Model):
|
|||
|
||||
|
||||
class UserSubscription(Subscription):
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
user = models.ForeignKey(User)
|
||||
is_unsub = models.BooleanField(
|
||||
_("désinscription"),
|
||||
default=False
|
||||
|
@ -35,10 +32,7 @@ class UserSubscription(Subscription):
|
|||
|
||||
|
||||
class GroupSubscription(Subscription):
|
||||
group = models.ForeignKey(
|
||||
Group,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
group = models.ForeignKey(Group)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("souscription en groupe")
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'equipment.apps.EquipmentConfig'
|
|
@ -1,133 +0,0 @@
|
|||
from django.contrib import admin
|
||||
from django import forms
|
||||
|
||||
from .models import Equipment, EquipmentDefault, EquipmentRevision, EquipmentCategory, EquipmentLost, EquipmentAttributeValue, EquipmentAttribute, EquipmentStorage
|
||||
from .fields import IdField, IdWidget
|
||||
|
||||
|
||||
class IdForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'min_value' in kwargs:
|
||||
kwargs.pop('min_value')
|
||||
if 'num_choices' in kwargs:
|
||||
num_choices = kwargs.pop('num_choices')
|
||||
else:
|
||||
num_choices = None
|
||||
super(IdForm, self).__init__(*args, **kwargs)
|
||||
|
||||
for field in self.instance._meta.fields:
|
||||
if isinstance(field, IdField):
|
||||
if num_choices is None:
|
||||
choices = []
|
||||
else:
|
||||
choices = [(k, str(k)) for k in range(1, num_choices+1)]
|
||||
self.fields[field.name].choices = choices
|
||||
self.fields[field.name].widget = IdWidget(choices=self.fields[field.name].choices)
|
||||
|
||||
|
||||
class IdFormset(forms.models.BaseInlineFormSet):
|
||||
def get_form_kwargs(self, index):
|
||||
kwargs = super().get_form_kwargs(index)
|
||||
if self.instance:
|
||||
kwargs["num_choices"] = self.instance.stock
|
||||
return kwargs
|
||||
|
||||
|
||||
class EquipmentRevisionExtraInline(admin.TabularInline):
|
||||
model = EquipmentRevision
|
||||
extra = 0
|
||||
form = IdForm
|
||||
formset = IdFormset
|
||||
classes = ['collapse']
|
||||
|
||||
|
||||
class EquipmentDefaultExtraInline(admin.TabularInline):
|
||||
model = EquipmentDefault
|
||||
extra = 0
|
||||
form = IdForm
|
||||
formset = IdFormset
|
||||
classes = ['collapse']
|
||||
|
||||
|
||||
class EquipmentLostExtraInline(admin.TabularInline):
|
||||
model = EquipmentLost
|
||||
extra = 0
|
||||
form = IdForm
|
||||
formset = IdFormset
|
||||
classes = ['collapse']
|
||||
|
||||
|
||||
class EquipmentAttributeValueInline(admin.TabularInline):
|
||||
model = EquipmentAttributeValue
|
||||
extra = 0
|
||||
|
||||
|
||||
class CategoryAdmin(admin.ModelAdmin):
|
||||
readonly_fields = ['full_name_p']
|
||||
list_display = ['name', 'parent', "full_name_p"]
|
||||
ordering = ['name', 'parent']
|
||||
search_fields = ['name',]
|
||||
autocomplete_fields = ['parent', ]
|
||||
|
||||
class StorageAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
ordering = ['name']
|
||||
search_fields = ['name']
|
||||
|
||||
|
||||
class EquipmentAttributeAdmin(admin.ModelAdmin):
|
||||
list_display = ['name']
|
||||
ordering = ['name']
|
||||
search_fields = ['name',]
|
||||
|
||||
|
||||
class EquipmentAdmin(admin.ModelAdmin):
|
||||
save_as_continue = True
|
||||
save_on_top = True
|
||||
autocomplete_fields = ['category', 'storage', ]
|
||||
readonly_fields = ['full_category_p',
|
||||
'added_at',
|
||||
'modified_at',
|
||||
'stock_aviable_p',
|
||||
'ids_aviable_p',
|
||||
'stock_lost_p',
|
||||
'ids_lost_p',
|
||||
]
|
||||
list_display = ['name', 'stock', 'owner', 'category', 'storage', 'modified_at']
|
||||
fieldsets = (
|
||||
('Général', {
|
||||
'fields': ('name', 'owner', 'stock', )
|
||||
}),
|
||||
('Info stock',
|
||||
{
|
||||
'fields': (
|
||||
'stock_aviable_p',
|
||||
'ids_aviable_p',
|
||||
'stock_lost_p',
|
||||
'ids_lost_p',
|
||||
),
|
||||
}),
|
||||
('Attributs', {
|
||||
'fields': ('category', 'full_category_p', 'storage'),
|
||||
}),
|
||||
('Description', {
|
||||
'fields': ('description', 'added_at', 'modified_at',),
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
ordering = ['name', 'owner', 'category']
|
||||
inlines = [EquipmentAttributeValueInline,
|
||||
EquipmentDefaultExtraInline,
|
||||
EquipmentLostExtraInline,
|
||||
EquipmentRevisionExtraInline]
|
||||
search_fields = ['name', 'description',]
|
||||
list_filter = ['owner', 'category', 'storage', ]
|
||||
|
||||
|
||||
admin.site.register(Equipment, EquipmentAdmin)
|
||||
admin.site.register(EquipmentCategory, CategoryAdmin)
|
||||
admin.site.register(EquipmentStorage, StorageAdmin)
|
||||
admin.site.register(EquipmentAttribute, EquipmentAttributeAdmin)
|
|
@ -1,7 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class EquipmentConfig(AppConfig):
|
||||
name = 'equipment'
|
||||
verbose_name = _("Équipement")
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
from django.db import models
|
||||
from django import forms
|
||||
|
||||
|
||||
#class IdWidget(AdminMultipleChoiceFieldWidget):
|
||||
class IdWidget(forms.widgets.CheckboxSelectMultiple):
|
||||
template_name = 'equipment/widgets/checkbox_select.html'
|
||||
option_template_name = 'equipment/widgets/checkbox_option.html'
|
||||
def __init__(self, *args, **kwargs):
|
||||
# nb_items = kwargs.pop('nb_items')
|
||||
# kwargs['choices'] = list(range(1, nb_items+1))
|
||||
super(IdWidget, self).__init__(*args, **kwargs)
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('css/idwidget.css',)
|
||||
}
|
||||
|
||||
class IdFormField(forms.MultipleChoiceField):
|
||||
#widget = IdWidget
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'min_value' in kwargs:
|
||||
kwargs.pop('min_value')
|
||||
if 'max_value' in kwargs:
|
||||
kwargs.pop('max_value')
|
||||
super(IdFormField, self).__init__(*args, **kwargs)
|
||||
|
||||
class IdField(models.BigIntegerField):
|
||||
def parse_integer(self, n):
|
||||
res = []
|
||||
k = 1
|
||||
while(n > 0):
|
||||
if n & 1:
|
||||
res.append(k)
|
||||
n >>= 1
|
||||
k += 1
|
||||
return res
|
||||
|
||||
def from_db_value(self, value, expression, connection):
|
||||
if value is None:
|
||||
return value
|
||||
return self.parse_integer(value)
|
||||
|
||||
def to_python(self, value):
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
return self.parse_integer(value)
|
||||
|
||||
def get_prep_value(self, value):
|
||||
res = 0
|
||||
for b in value:
|
||||
res |= 1 << (int(b)-1)
|
||||
return res
|
||||
|
||||
def __init__(self, separator=",", *args, **kwargs):
|
||||
self.separator = separator
|
||||
super(IdField, self).__init__(*args, **kwargs)
|
||||
self.validators = [] # TODO : validateurs pertinents
|
||||
|
||||
def deconstruct(self):
|
||||
name, path, args, kwargs = super(IdField, self).deconstruct()
|
||||
# Only include kwarg if it's not the default
|
||||
if self.separator != ",":
|
||||
kwargs['separator'] = self.separator
|
||||
return name, path, args, kwargs
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
# This is a fairly standard way to set up some defaults
|
||||
# while letting the caller override them.
|
||||
defaults = {'form_class': IdFormField}
|
||||
defaults.update(kwargs)
|
||||
return super(IdField, self).formfield(**defaults)
|
|
@ -1,11 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-06 17:29
|
||||
# Generated by Django 1.11.4 on 2017-08-12 12:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import equipment.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -34,7 +32,7 @@ class Migration(migrations.Migration):
|
|||
name='EquipmentAttribution',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.BigIntegerField(verbose_name='quantité attribuée')),
|
||||
('amount', models.PositiveSmallIntegerField(verbose_name='quantité attribuée')),
|
||||
('remarks', models.TextField(verbose_name="remarques concernant l'attribution")),
|
||||
('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')),
|
||||
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')),
|
||||
|
@ -44,45 +42,12 @@ class Migration(migrations.Migration):
|
|||
'verbose_name_plural': 'attributions de matériel',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentCategory',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='nom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'catégories',
|
||||
'verbose_name_plural': 'catégories',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentOwner',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='nom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'propriétaire de matériel',
|
||||
'verbose_name_plural': 'propriétaires de matériel',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentPole',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='nom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'pôle',
|
||||
'verbose_name_plural': 'pôle',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentRemark',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('remark', models.TextField(verbose_name='remarque sur le matériel')),
|
||||
('ids', equipment.fields.IdField()),
|
||||
('amount', models.PositiveSmallIntegerField(verbose_name='quantité concernée')),
|
||||
('is_broken', models.BooleanField()),
|
||||
('is_lost', models.BooleanField()),
|
||||
('equipment', models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')),
|
||||
|
@ -92,43 +57,14 @@ class Migration(migrations.Migration):
|
|||
'verbose_name_plural': 'remarques sur le matériel',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentRevision',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateField(default=datetime.date.today, verbose_name='date')),
|
||||
('remark', models.TextField(verbose_name='remarque sur la révision')),
|
||||
('ids', equipment.fields.IdField()),
|
||||
('equipment', models.ForeignKey(help_text='Matériel concerné par les révisions', on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='equipment.Equipment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'révision de matériel',
|
||||
'verbose_name_plural': 'révisions de matériel',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='activities',
|
||||
field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='event',
|
||||
field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question.", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentOwner'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='pole',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentPole'),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-07 16:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import equipment.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EquipmentDefault',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('remark', models.TextField(verbose_name='remarque sur le défaut')),
|
||||
('ids', equipment.fields.IdField()),
|
||||
('is_unusable', models.BooleanField(verbose_name='inutilisable')),
|
||||
('send_repare', models.BooleanField(verbose_name='à envoyer réparareur')),
|
||||
('equipment', models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'defaut matériel',
|
||||
'verbose_name_plural': 'défauts sur le matériel',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentLost',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('lost_at', models.DateField(default=datetime.date.today, verbose_name='perdu le')),
|
||||
('ids', equipment.fields.IdField()),
|
||||
('equipment', models.ForeignKey(help_text='Matériel concerné par la perte', on_delete=django.db.models.deletion.CASCADE, related_name='losts', to='equipment.Equipment')),
|
||||
],
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='equipmentremark',
|
||||
name='equipment',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EquipmentRemark',
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-07 18:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0002_auto_20180807_1658'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='equipment',
|
||||
name='owner',
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EquipmentOwner',
|
||||
),
|
||||
]
|
|
@ -1,22 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-07 18:44
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('auth', '0008_alter_user_username_max_length'),
|
||||
('equipment', '0003_auto_20180807_1843'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group'),
|
||||
),
|
||||
]
|
|
@ -1,28 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-08 10:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0004_equipment_owner'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='equipment',
|
||||
name='pole',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipmentcategory',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='EquipmentPole',
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-08 13:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0005_auto_20180808_1013'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipmentcategory',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentCategory'),
|
||||
),
|
||||
]
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-08 14:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0006_auto_20180808_1354'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EquipmentAttribute',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, unique=True, verbose_name='nom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'attribut',
|
||||
'verbose_name_plural': 'attributs',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='EquipmentAttributeValue',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('value', models.CharField(max_length=200, verbose_name='valeur')),
|
||||
('attribute', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentAttribute')),
|
||||
('equipment', models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='attributes', to='equipment.Equipment')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'attribut de matériel',
|
||||
'verbose_name_plural': 'attributs de matériel',
|
||||
},
|
||||
),
|
||||
]
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-08 14:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0007_equipmentattribute_equipmentattributevalue'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentCategory'),
|
||||
),
|
||||
]
|
|
@ -1,28 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-09 12:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0008_auto_20180808_1454'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='added_at',
|
||||
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2018, 8, 9, 12, 0, 50, 140250, tzinfo=utc), verbose_name='ajouté le'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='modified_at',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='dernière modification'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-09 12:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0009_auto_20180809_1200'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='stock',
|
||||
field=models.PositiveSmallIntegerField(verbose_name='quantité totale'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-09 14:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0010_auto_20180809_1211'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='description',
|
||||
field=models.TextField(blank=True, verbose_name='description'),
|
||||
),
|
||||
]
|
|
@ -1,61 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-20 11:24
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0011_auto_20180809_1405'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='category',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentCategory', verbose_name='catégorie'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='auth.Group', verbose_name='propriétaire'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentattributevalue',
|
||||
name='attribute',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.EquipmentAttribute', verbose_name='attribut'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentattributevalue',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='attributes', to='equipment.Equipment', verbose_name='matériel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentattribution',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment', verbose_name='matériel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentcategory',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='equipment.EquipmentCategory', verbose_name='parent'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentdefault',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(help_text='Matériel concerné par le defaut', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment', verbose_name='matériel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentlost',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(help_text='Matériel concerné par la perte', on_delete=django.db.models.deletion.CASCADE, related_name='losts', to='equipment.Equipment', verbose_name='matériel'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentrevision',
|
||||
name='equipment',
|
||||
field=models.ForeignKey(help_text='Matériel concerné par les révisions', on_delete=django.db.models.deletion.CASCADE, related_name='revisions', to='equipment.Equipment', verbose_name='matériel'),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-26 17:49
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0012_auto_20180820_1124'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipmentattribution',
|
||||
name='remarks',
|
||||
field=models.TextField(blank=True, verbose_name="remarques concernant l'attribution"),
|
||||
),
|
||||
]
|
|
@ -1,26 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-26 22:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0013_auto_20180826_1949'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='equipment',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='auth.Group', verbose_name='propriétaire'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='equipmentcategory',
|
||||
name='parent',
|
||||
field=models.ForeignKey(blank=True, default=None, help_text='merci de ne pas faire de référence cyclique', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='equipment.EquipmentCategory', verbose_name='parent'),
|
||||
),
|
||||
]
|
|
@ -1,26 +0,0 @@
|
|||
# Generated by Django 2.1.5 on 2019-03-18 11:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0014_auto_20180827_0005'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='EquipmentStorage',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='nom')),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='equipment',
|
||||
name='storage',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='equipment.EquipmentStorage', verbose_name='stockage'),
|
||||
),
|
||||
]
|
|
@ -1,17 +0,0 @@
|
|||
# Generated by Django 2.1.5 on 2019-03-18 11:47
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('equipment', '0015_auto_20190318_1245'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='equipmentstorage',
|
||||
options={'verbose_name': 'stockage', 'verbose_name_plural': 'stockages'},
|
||||
),
|
||||
]
|
|
@ -1,98 +1,7 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import Group
|
||||
from event.models import Activity, EventSpecificMixin
|
||||
from django.db.models import Q
|
||||
|
||||
from .fields import IdField
|
||||
|
||||
from datetime import date
|
||||
|
||||
|
||||
class EquipmentStorage(models.Model):
|
||||
name = models.CharField(
|
||||
_("nom"),
|
||||
max_length=200,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("stockage")
|
||||
verbose_name_plural = _("stockages")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class EquipmentCategory(models.Model):
|
||||
name = models.CharField(
|
||||
_("nom"),
|
||||
max_length=200,
|
||||
)
|
||||
parent = models.ForeignKey(
|
||||
'self',
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="children",
|
||||
help_text=_("merci de ne pas faire de référence cyclique"),
|
||||
verbose_name=_("parent"),
|
||||
)
|
||||
|
||||
def has_parent(self, cat):
|
||||
current = self
|
||||
for k in range(100):
|
||||
if current is None:
|
||||
return False
|
||||
if current == cat:
|
||||
return True
|
||||
current = current.parent
|
||||
|
||||
def full_name(self):
|
||||
current = self
|
||||
res = ""
|
||||
for k in range(100):
|
||||
res = "/{current}{old}".format(
|
||||
current=current.name,
|
||||
old=res)
|
||||
if current.parent is None:
|
||||
break
|
||||
current = current.parent
|
||||
return res
|
||||
|
||||
full_name.short_description = _("Chemin complet")
|
||||
full_name_p = property(full_name)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("catégories")
|
||||
verbose_name_plural = _("catégories")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if self.pk:
|
||||
done = False
|
||||
current = self
|
||||
while not done:
|
||||
if current.parent == self:
|
||||
self.parent = None
|
||||
done = True
|
||||
elif current.parent is None:
|
||||
done = True
|
||||
current = current.parent
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
|
||||
class EquipmentQuerySet(models.QuerySet):
|
||||
def in_category(self, cat):
|
||||
filtre = Q(id__lt=0)
|
||||
childs_id = [c.id for c in EquipmentCategory.objects.all()
|
||||
if c.has_parent(cat)]
|
||||
for pk in childs_id:
|
||||
filtre |= Q(category__id=pk)
|
||||
return self.filter(filtre)
|
||||
|
||||
|
||||
class Equipment(EventSpecificMixin, models.Model):
|
||||
|
@ -100,98 +9,13 @@ class Equipment(EventSpecificMixin, models.Model):
|
|||
_("nom du matériel"),
|
||||
max_length=200,
|
||||
)
|
||||
stock = models.PositiveSmallIntegerField(_("quantité totale"))
|
||||
description = models.TextField(
|
||||
_("description"),
|
||||
blank=True,
|
||||
)
|
||||
stock = models.PositiveSmallIntegerField(_("quantité disponible"))
|
||||
description = models.TextField(_("description"))
|
||||
activities = models.ManyToManyField(
|
||||
Activity,
|
||||
related_name="equipment",
|
||||
through="EquipmentAttribution",
|
||||
)
|
||||
owner = models.ForeignKey(
|
||||
Group,
|
||||
verbose_name=_("propriétaire"),
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
category = models.ForeignKey(
|
||||
EquipmentCategory,
|
||||
verbose_name=_("catégorie"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
storage = models.ForeignKey(
|
||||
EquipmentStorage,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
verbose_name=_("stockage"),
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
added_at = models.DateTimeField(
|
||||
_("ajouté le"),
|
||||
auto_now_add=True,
|
||||
)
|
||||
modified_at = models.DateTimeField(
|
||||
_("dernière modification"),
|
||||
auto_now=True,
|
||||
)
|
||||
|
||||
objects = EquipmentQuerySet.as_manager()
|
||||
|
||||
def is_in_category(self, cat):
|
||||
current = self.category
|
||||
for k in range(100):
|
||||
if current is None:
|
||||
return False
|
||||
if current == cat:
|
||||
return True
|
||||
current = current.parent
|
||||
|
||||
def ids_aviable(self):
|
||||
if self.stock is None:
|
||||
return []
|
||||
res = list(map(lambda x: x+1, range(self.stock)))
|
||||
for lost in self.losts.all():
|
||||
res = [x
|
||||
for x in res
|
||||
if x not in lost.ids]
|
||||
# TODO cassé
|
||||
# TODO utilisés
|
||||
return res
|
||||
|
||||
def ids_lost(self):
|
||||
res = []
|
||||
for lost in self.losts.all():
|
||||
res = res + [x
|
||||
for x in lost.ids
|
||||
if x not in res]
|
||||
return res
|
||||
|
||||
def stock_aviable(self):
|
||||
aviable = self.ids_aviable()
|
||||
return len(aviable)
|
||||
|
||||
def stock_lost(self):
|
||||
return len(self.ids_lost())
|
||||
|
||||
def full_category(self):
|
||||
return self.category.full_name()
|
||||
|
||||
full_category.short_description = _("Chemin complet")
|
||||
ids_aviable.short_description = _("disponibles")
|
||||
ids_lost.short_description = _("perdus")
|
||||
stock_aviable.short_description = _("quantité disponible")
|
||||
stock_lost.short_description = _("quantité perdue")
|
||||
|
||||
full_category_p = property(full_category)
|
||||
ids_aviable_p = property(ids_aviable)
|
||||
ids_lost_p = property(ids_lost)
|
||||
stock_aviable_p = property(stock_aviable)
|
||||
stock_lost_p = property(stock_lost)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("matériel")
|
||||
|
@ -201,63 +25,11 @@ class Equipment(EventSpecificMixin, models.Model):
|
|||
return self.name
|
||||
|
||||
|
||||
class EquipmentAttribute(models.Model):
|
||||
name = models.CharField(
|
||||
_("nom"),
|
||||
max_length=200,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("attribut")
|
||||
verbose_name_plural = _("attributs")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class EquipmentAttributeValue(models.Model):
|
||||
equipment = models.ForeignKey(
|
||||
Equipment,
|
||||
on_delete=models.CASCADE,
|
||||
related_name="attributes",
|
||||
verbose_name=_("matériel"),
|
||||
help_text=_("Matériel concerné par le defaut"),
|
||||
)
|
||||
attribute = models.ForeignKey(
|
||||
EquipmentAttribute,
|
||||
verbose_name=_("attribut"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
value = models.CharField(
|
||||
_("valeur"),
|
||||
max_length=200,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("attribut de matériel")
|
||||
verbose_name_plural = _("attributs de matériel")
|
||||
|
||||
def __str__(self):
|
||||
return "{attr}={value}".format(attr=self.attribute.name,
|
||||
value=self.value)
|
||||
|
||||
|
||||
class EquipmentAttribution(models.Model):
|
||||
equipment = models.ForeignKey(
|
||||
Equipment,
|
||||
verbose_name=_("matériel"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
activity = models.ForeignKey(
|
||||
Activity,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
amount = models.BigIntegerField(_("quantité attribuée"))
|
||||
remarks = models.TextField(
|
||||
_("remarques concernant l'attribution"),
|
||||
blank=True,
|
||||
)
|
||||
equipment = models.ForeignKey(Equipment)
|
||||
activity = models.ForeignKey(Activity)
|
||||
amount = models.PositiveSmallIntegerField(_("quantité attribuée"))
|
||||
remarks = models.TextField(_("remarques concernant l'attribution"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("attribution de matériel")
|
||||
|
@ -265,72 +37,30 @@ class EquipmentAttribution(models.Model):
|
|||
|
||||
def __str__(self):
|
||||
return "%s (%d) -> %s" % (self.equipment.name,
|
||||
self.amount,
|
||||
self.amout,
|
||||
self.activity.get_herited('title'))
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if (self.equipment.event
|
||||
and self.equipment.event != self.activity.event):
|
||||
if self.equipment.event and self.equipment.event != self.activity.event:
|
||||
raise ValidationError
|
||||
|
||||
super(EquipmentAttribution, self).save(*args, **kwargs)
|
||||
|
||||
|
||||
class EquipmentDefault(models.Model):
|
||||
remark = models.TextField(_("remarque sur le défaut"))
|
||||
class EquipmentRemark(models.Model):
|
||||
remark = models.TextField(_("remarque sur le matériel"))
|
||||
equipment = models.ForeignKey(
|
||||
Equipment,
|
||||
verbose_name=_("matériel"),
|
||||
on_delete=models.CASCADE,
|
||||
related_name="remarks",
|
||||
help_text=_("Matériel concerné par le defaut"),
|
||||
help_text=_("Matériel concerné par la remarque"),
|
||||
)
|
||||
ids = IdField()
|
||||
is_unusable = models.BooleanField(_("inutilisable"))
|
||||
send_repare = models.BooleanField(_("à envoyer réparareur"))
|
||||
amount = models.PositiveSmallIntegerField(_("quantité concernée"))
|
||||
is_broken = models.BooleanField()
|
||||
is_lost = models.BooleanField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("defaut matériel")
|
||||
verbose_name_plural = _("défauts sur le matériel")
|
||||
|
||||
def __str__(self):
|
||||
return "%s : %s" % (self.equipment.name,
|
||||
self.remark)
|
||||
|
||||
|
||||
class EquipmentLost(models.Model):
|
||||
lost_at = models.DateField(
|
||||
_("perdu le"),
|
||||
default=date.today,
|
||||
)
|
||||
equipment = models.ForeignKey(
|
||||
Equipment,
|
||||
verbose_name=_("matériel"),
|
||||
on_delete=models.CASCADE,
|
||||
related_name="losts",
|
||||
help_text=_("Matériel concerné par la perte"),
|
||||
)
|
||||
ids = IdField()
|
||||
|
||||
|
||||
class EquipmentRevision(models.Model):
|
||||
date = models.DateField(
|
||||
_("date"),
|
||||
default=date.today,
|
||||
)
|
||||
equipment = models.ForeignKey(
|
||||
Equipment,
|
||||
verbose_name=_("matériel"),
|
||||
on_delete=models.CASCADE,
|
||||
related_name="revisions",
|
||||
help_text=_("Matériel concerné par les révisions"),
|
||||
)
|
||||
remark = models.TextField(_("remarque sur la révision"))
|
||||
ids = IdField()
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("révision de matériel")
|
||||
verbose_name_plural = _("révisions de matériel")
|
||||
verbose_name = _("remarque sur matériel")
|
||||
verbose_name_plural = _("remarques sur le matériel")
|
||||
|
||||
def __str__(self):
|
||||
return "%s : %s" % (self.equipment.name,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
.nice_select input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nice_select input[type="checkbox"]:checked + label {
|
||||
background: red;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.nice_select ul {
|
||||
display: inline-block;
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models import Q
|
||||
from django.http.request import QueryDict
|
||||
|
||||
import django_filters
|
||||
from django_filters.widgets import LinkWidget
|
||||
from django_tables2.utils import A
|
||||
import django_tables2 as tables
|
||||
|
||||
from .models import Equipment, EquipmentCategory
|
||||
|
||||
|
||||
class EquipmentFilter(django_filters.FilterSet):
|
||||
owner = django_filters.ModelChoiceFilter(
|
||||
field_name='owner',
|
||||
queryset=Group.objects.all(),
|
||||
widget=LinkWidget(),
|
||||
)
|
||||
category = django_filters.ModelChoiceFilter(
|
||||
field_name='category',
|
||||
queryset=EquipmentCategory.objects.all(),
|
||||
widget=LinkWidget(),
|
||||
method='filter_category',
|
||||
)
|
||||
|
||||
def filter_category(self, queryset, name, value):
|
||||
return queryset.in_category(value)
|
||||
|
||||
def get_categories(self, qs):
|
||||
"""
|
||||
rend les catégories qui servent à filtrer les Equipments de qs
|
||||
ie les catégories des equipments et tous leurs parents
|
||||
"""
|
||||
filtre = Q(id__lt=0)
|
||||
for eq in qs:
|
||||
current = eq.category
|
||||
for k in range(100):
|
||||
if current is None:
|
||||
break
|
||||
filtre |= Q(id=current.id)
|
||||
current = current.parent
|
||||
return EquipmentCategory.objects.filter(filtre)
|
||||
|
||||
def __init__(self, data=None, queryset=None, *, request=None, prefix=None):
|
||||
# On que les requêtes vides rendent quelque chose
|
||||
if data is None:
|
||||
data = QueryDict('category&owner')
|
||||
super().__init__(data=data, queryset=queryset,
|
||||
request=request, prefix=prefix)
|
||||
if self.queryset is not None:
|
||||
for filter_ in self.filters.values():
|
||||
if filter_.queryset.model == Group:
|
||||
own_ids = [eq.owner.id for eq in self.queryset
|
||||
if eq.owner is not None]
|
||||
filtre = Q(id__lt=0)
|
||||
for own_id in own_ids:
|
||||
filtre |= Q(id=own_id)
|
||||
filter_.queryset = Group.objects.filter(filtre)
|
||||
if filter_.queryset.model == EquipmentCategory:
|
||||
filter_.queryset = self.get_categories(self.queryset)
|
||||
|
||||
class Meta:
|
||||
model = Equipment
|
||||
fields = ['category', 'owner']
|
||||
|
||||
|
||||
class AbstractEquipmentTable(tables.Table):
|
||||
stock_aviable_p = tables.Column(
|
||||
accessor=A('stock_aviable_p'),
|
||||
orderable=False, # TODO le rendre ordorable
|
||||
verbose_name=_("Quantité disponible"),
|
||||
)
|
||||
full_category_p = tables.Column(
|
||||
accessor=A('full_category_p'),
|
||||
order_by=('category'),
|
||||
verbose_name=_("Catégorie"),
|
||||
)
|
||||
name = tables.LinkColumn(
|
||||
'equipment:detail',
|
||||
args=[A('pk')],
|
||||
verbose_name=_("Matériel"),
|
||||
)
|
||||
admin = tables.LinkColumn(
|
||||
'admin:equipment_equipment_change',
|
||||
attrs={
|
||||
'a': {'class': 'glyphicon glyphicon-cog'}
|
||||
},
|
||||
text="",
|
||||
orderable=False,
|
||||
args=[A('pk')],
|
||||
verbose_name=_(""),
|
||||
)
|
||||
|
||||
def before_render(self, request):
|
||||
if (request.user.is_staff and
|
||||
request.user.has_perm('equipment_change_equipment')):
|
||||
self.columns.show('admin')
|
||||
else:
|
||||
self.columns.hide('admin')
|
||||
|
||||
|
||||
class EquipmentTable(AbstractEquipmentTable):
|
||||
class Meta:
|
||||
model = Equipment
|
||||
template_name = 'equipment/tables/bootstrap-responsive.html'
|
||||
fields = ['name', 'stock', 'owner', ]
|
||||
sequence = ['admin', 'name', 'stock', 'stock_aviable_p',
|
||||
'full_category_p', 'owner', ]
|
|
@ -1,13 +0,0 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% load i18n staticfiles %}
|
||||
|
||||
{% block title %}{% trans "Matériel" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ equipment.name }}<a class="pull-right glyphicon glyphicon-cog" href="{% url 'admin:equipment_equipment_change' equipment.id %}"></a></h2>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
Coucou :)
|
||||
{% endblock %}
|
|
@ -1,50 +0,0 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% load i18n staticfiles %}
|
||||
|
||||
{% block title %}{% trans "Matériel" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="equipment">{% trans "Inventaire" %}</h1>
|
||||
<div class="module-list">
|
||||
<a href="{% url 'equipment:list' %}" class="module equipment">
|
||||
<span class="glyphicon glyphicon-list-alt"></span>
|
||||
{% trans "Tout le matériel" %}
|
||||
</a>
|
||||
<a href="#TODO" class="module equipment">
|
||||
<span class="glyphicon glyphicon-list-alt"></span>
|
||||
{% trans "Disponible" %}
|
||||
</a>
|
||||
</div>
|
||||
<h2 class="staff">{% trans "Liste par Propriétaire" %}</h2>
|
||||
<div class="module-list">
|
||||
{% for owner in owners %}
|
||||
<a href="{% url 'equipment:list_by_owner' owner.id %}" class="module staff">
|
||||
<span class="glyphicon glyphicon-user"></span>
|
||||
{{ owner.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<h2 class="equipment">{% trans "Liste par Catégorie" %}</h2>
|
||||
<div class="tree">
|
||||
<ul>
|
||||
{% for node in root_cat %}
|
||||
{% include "equipment/tree_cat.html" %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
<div class="heading">
|
||||
{{ nb_type }} <span class="sub">{% trans "référence" %}{{ nb_type|pluralize}}</span>
|
||||
</div>
|
||||
<div class="heading separator">
|
||||
{{ stock }} <span class="sub">{% trans "item" %}{{ stock|pluralize}}</span>
|
||||
</div>
|
||||
<div class="heading inverted small">
|
||||
{{ categories.count }} <span class="sub">{% trans "catégorie" %}{{ categories.count|pluralize}}</span>
|
||||
</div>
|
||||
<div class="heading inverted small ">
|
||||
{{ owners.count }} <span class="sub">{% trans "propriéaire" %}{{ owners.count|pluralize}}</span>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,30 +0,0 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load bootstrap3 %}
|
||||
{% load i18n staticfiles %}
|
||||
|
||||
{% block title %}{% trans "Matériel" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1 class="equipment">Inventaire</h1>
|
||||
{% if subtitle %}
|
||||
<h2 class="equipment">{{ subtitle }}</h2>
|
||||
{% endif %}
|
||||
{% render_table table %}
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
<div class="heading">
|
||||
{{ nb_type }} <span class="sub">{% trans "référence" %}{{ nb_type|pluralize}}</span>
|
||||
</div>
|
||||
<div class="heading separator">
|
||||
{{ stock }} <span class="sub">{% trans "item" %}{{ stock|pluralize}}</span>
|
||||
</div>
|
||||
{% if filter %}
|
||||
<div class="text inverted">
|
||||
<form id="filter_form" action="" method="get" class="form form-inline">
|
||||
{% bootstrap_form filter.form layout='horizontal' size='lg' %}
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -1,16 +0,0 @@
|
|||
{% extends 'equipment/tables/bootstrap.html' %}
|
||||
|
||||
{% block table-wrapper %}
|
||||
<div class="table-container table-responsive">
|
||||
{% block table %}
|
||||
{{ block.super }}
|
||||
{% endblock table %}
|
||||
|
||||
{% if table.page and table.paginator.num_pages > 1 %}
|
||||
{% block pagination %}
|
||||
{{ block.super }}
|
||||
{% endblock pagination %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock table-wrapper %}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
{% load django_tables2 %}
|
||||
{% load i18n %}
|
||||
{% block table-wrapper %}
|
||||
<div class="table-container">
|
||||
{% block table %}
|
||||
<table {% render_attrs table.attrs class="table table-striped" %}>
|
||||
{% block table.thead %}
|
||||
{% if table.show_header %}
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
<th {{ column.attrs.th.as_html }}>
|
||||
{% if column.orderable %}
|
||||
<a href="{% querystring table.prefixed_order_by_field=column.order_by_alias.next %}">{{ column.header }}</a>
|
||||
{% else %}
|
||||
{{ column.header }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
{% endblock table.thead %}
|
||||
{% block table.tbody %}
|
||||
<tbody>
|
||||
{% for row in table.paginated_rows %}
|
||||
{% block table.tbody.row %}
|
||||
<tr {{ row.attrs.as_html }}>
|
||||
{% for column, cell in row.items %}
|
||||
<td {{ column.attrs.td.as_html }}>{% if column.localize == None %}{{ cell }}{% else %}{% if column.localize %}{{ cell|localize }}{% else %}{{ cell|unlocalize }}{% endif %}{% endif %}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endblock table.tbody.row %}
|
||||
{% empty %}
|
||||
{% if table.empty_text %}
|
||||
{% block table.tbody.empty_text %}
|
||||
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
|
||||
{% endblock table.tbody.empty_text %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
{% endblock table.tbody %}
|
||||
{% block table.tfoot %}
|
||||
{% if table.has_footer %}
|
||||
<tfoot>
|
||||
<tr>
|
||||
{% for column in table.columns %}
|
||||
<td {{ column.attrs.tf.as_html }}>{{ column.footer }}</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</tfoot>
|
||||
{% endif %}
|
||||
{% endblock table.tfoot %}
|
||||
</table>
|
||||
{% endblock table %}
|
||||
|
||||
{% if table.page and table.paginator.num_pages > 1 %}
|
||||
{% block pagination %}
|
||||
<nav aria-label="Table navigation">
|
||||
<ul class="pagination">
|
||||
{% if table.page.has_previous %}
|
||||
{% block pagination.previous %}
|
||||
<li class="previous">
|
||||
<a href="{% querystring table.prefixed_page_field=table.page.previous_page_number %}">
|
||||
<span aria-hidden="true">«</span>
|
||||
{% trans 'previous' %}
|
||||
</a>
|
||||
</li>
|
||||
{% endblock pagination.previous %}
|
||||
{% endif %}
|
||||
{% if table.page.has_previous or table.page.has_next %}
|
||||
{% block pagination.range %}
|
||||
{% for p in table.page|table_page_range:table.paginator %}
|
||||
<li {% if p == table.page.number %}class="active"{% endif %}>
|
||||
{% if p == '...' %}
|
||||
<a href="#">{{ p }}</a>
|
||||
{% else %}
|
||||
<a href="{% querystring table.prefixed_page_field=p %}">
|
||||
{{ p }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endblock pagination.range %}
|
||||
{% endif %}
|
||||
|
||||
{% if table.page.has_next %}
|
||||
{% block pagination.next %}
|
||||
<li class="next">
|
||||
<a href="{% querystring table.prefixed_page_field=table.page.next_page_number %}">
|
||||
{% trans 'next' %}
|
||||
<span aria-hidden="true">»</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endblock pagination.next %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endblock pagination %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock table-wrapper %}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
<li> <a href="{% url "equipment:list_by_category" node.id %}"><span class="category_node">{{node.name}}</span></a>
|
||||
{%if node.children %}
|
||||
<ul>
|
||||
{%for ch in node.children.all %}
|
||||
{%with node=ch template_name="equipment/tree_cat.html" %}
|
||||
{%include template_name%}
|
||||
{%endwith%}
|
||||
{%endfor%}
|
||||
</ul>
|
||||
{%endif%}
|
||||
</li>
|
|
@ -1 +0,0 @@
|
|||
{% include "django/forms/widgets/input.html" %}<label{% if widget.attrs.id %} for="{{ widget.attrs.id }}"{% endif %}>{{ widget.label }}</label>
|
|
@ -1,5 +0,0 @@
|
|||
{% with id=widget.attrs.id %}<ul{% if id %} id="{{ id }}"{% endif %}{% if widget.attrs.class %} class="{{ widget.attrs.class }} nice_select"{% endif %} style="display: inline-block">{% for group, options, index in widget.optgroups %}{% if group %}
|
||||
<li>{{ group }}<ul{% if id %} id="{{ id }}_{{ index }}"{% endif %}>{% endif %}{% for option in options %}
|
||||
<li style="list-style-type: none;">{% include option.template_name with widget=option %}</li>{% endfor %}{% if group %}
|
||||
</ul></li>{% endif %}{% endfor %}
|
||||
</ul>{% endwith %}
|
|
@ -1,11 +0,0 @@
|
|||
from django.conf.urls import url
|
||||
from .views import EquipmentList, EquipmentView, EquipmentListByCategory, EquipmentListByOwner, EquipmentHome
|
||||
|
||||
app_name = 'equipment'
|
||||
urlpatterns = [
|
||||
url(r'^$', EquipmentHome.as_view(), name='home'),
|
||||
url(r'^all/$', EquipmentList.as_view(), name='list'),
|
||||
url(r'^(?P<pk>[0-9]+)/$', EquipmentView.as_view(), name='detail'),
|
||||
url(r'^c/(?P<pk>[0-9]+)/$', EquipmentListByCategory.as_view(), name='list_by_category'),
|
||||
url(r'^o/(?P<pk>[0-9]+)/$', EquipmentListByOwner.as_view(), name='list_by_owner'),
|
||||
]
|
|
@ -1,97 +0,0 @@
|
|||
from .models import Equipment, EquipmentCategory
|
||||
from django.contrib.auth.models import Group
|
||||
from django.db.models import Sum
|
||||
from django.views.generic import DetailView, ListView
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
|
||||
from django_filters.views import FilterView
|
||||
from django_tables2.views import SingleTableMixin
|
||||
|
||||
from .tables import EquipmentTable, EquipmentFilter
|
||||
|
||||
|
||||
class EquipmentHome(LoginRequiredMixin, ListView):
|
||||
template_name = 'equipment/home.html'
|
||||
context_object_name = 'categories'
|
||||
model = EquipmentCategory
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
# TODO remplacer par les vrais owners
|
||||
context['owners'] = Group.objects.all()
|
||||
categories = (EquipmentCategory.objects.order_by('name')
|
||||
.prefetch_related('children'))
|
||||
context['root_cat'] = categories.filter(parent=None)
|
||||
queryset = Equipment.objects.all()
|
||||
context['stock'] = queryset.aggregate(Sum('stock'))['stock__sum']
|
||||
context['nb_type'] = queryset.count()
|
||||
return context
|
||||
|
||||
|
||||
class EquipmentListAbstract(LoginRequiredMixin, SingleTableMixin,FilterView):
|
||||
table_class = EquipmentTable
|
||||
filterset_class = EquipmentFilter
|
||||
template_name = 'equipment/list.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['stock'] = self.queryset.aggregate(Sum('stock'))['stock__sum']
|
||||
context['nb_type'] = self.queryset.count()
|
||||
return context
|
||||
|
||||
|
||||
class EquipmentList(EquipmentListAbstract):
|
||||
def get_queryset(self):
|
||||
self.queryset = Equipment.objects.all()
|
||||
return self.queryset
|
||||
|
||||
|
||||
class EquipmentListByCategory(EquipmentListAbstract):
|
||||
def get_category(self):
|
||||
try:
|
||||
pk = self.kwargs.get('pk')
|
||||
except KeyError:
|
||||
raise AttributeError(
|
||||
"View %s must be called with an object "
|
||||
"pk in the URLconf." % self.__class__.__name__
|
||||
)
|
||||
return EquipmentCategory.objects.get(id=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
cat = self.get_category()
|
||||
self.queryset = Equipment.objects.all().in_category(cat)
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
cat = self.get_category()
|
||||
context['subtitle'] = "Dans {cat}".format(cat=cat.full_name())
|
||||
return context
|
||||
|
||||
|
||||
class EquipmentListByOwner(EquipmentListAbstract):
|
||||
def get_owner(self):
|
||||
try:
|
||||
pk = self.kwargs.get('pk')
|
||||
except KeyError:
|
||||
raise AttributeError(
|
||||
"View %s must be called with an object "
|
||||
"pk in the URLconf." % self.__class__.__name__
|
||||
)
|
||||
return Group.objects.get(id=pk)
|
||||
|
||||
def get_queryset(self):
|
||||
owner = self.get_owner()
|
||||
self.queryset = Equipment.objects.filter(owner=owner)
|
||||
return self.queryset
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
owner = self.get_owner()
|
||||
context['subtitle'] = "Matériel de {owner}".format(owner=owner)
|
||||
return context
|
||||
|
||||
|
||||
class EquipmentView(LoginRequiredMixin, DetailView):
|
||||
model = Equipment
|
||||
template_name = 'equipment/detail.html'
|
|
@ -2,6 +2,6 @@ import os
|
|||
from channels.asgi import get_channel_layer
|
||||
|
||||
if "DJANGO_SETTINGS_MODULE" not in os.environ:
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings")
|
||||
|
||||
channel_layer = get_channel_layer()
|
|
@ -10,8 +10,6 @@ We also load the secrets in this file.
|
|||
|
||||
import os
|
||||
from . import secret
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
|
||||
|
||||
def import_secret(name):
|
||||
|
@ -27,7 +25,6 @@ def import_secret(name):
|
|||
|
||||
SECRET_KEY = import_secret("SECRET_KEY")
|
||||
ADMINS = import_secret("ADMINS")
|
||||
SERVER_EMAIL = import_secret("SERVER_EMAIL")
|
||||
|
||||
DBNAME = import_secret("DBNAME")
|
||||
DBUSER = import_secret("DBUSER")
|
||||
|
@ -47,46 +44,31 @@ BASE_DIR = os.path.dirname(
|
|||
|
||||
|
||||
INSTALLED_APPS = [
|
||||
# 'shared.apps.CustomAdminConfig',
|
||||
'communication.apps.CommunicationConfig',
|
||||
'equipment.apps.EquipmentConfig',
|
||||
'event.apps.EventConfig',
|
||||
'users.apps.UsersConfig',
|
||||
'shared.apps.SharedConfig',
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.sites',
|
||||
|
||||
# 'channels',
|
||||
# 'rest_framework',
|
||||
'channels',
|
||||
'rest_framework',
|
||||
'bootstrapform',
|
||||
'widget_tweaks',
|
||||
'taggit',
|
||||
'django_tables2',
|
||||
'django_filters',
|
||||
'bootstrap3',
|
||||
|
||||
'allauth_ens',
|
||||
'allauth',
|
||||
'allauth.account',
|
||||
'allauth.socialaccount',
|
||||
'allauth_cas',
|
||||
'allauth_ens.providers.clipper',
|
||||
|
||||
# 'api',
|
||||
'communication',
|
||||
'equipment',
|
||||
'event',
|
||||
'shared',
|
||||
'users',
|
||||
'api',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
MIDDLEWARE_CLASSES = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
# 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
@ -99,7 +81,7 @@ REST_FRAMEWORK = {
|
|||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||
}
|
||||
|
||||
ROOT_URLCONF = 'poulpe.urls'
|
||||
ROOT_URLCONF = 'evenementiel.urls'
|
||||
|
||||
STATIC_URL = "/static/"
|
||||
MEDIA_URL = "/media/"
|
||||
|
@ -137,19 +119,19 @@ DATABASES = {
|
|||
}
|
||||
}
|
||||
|
||||
# CHANNEL_LAYERS = {
|
||||
# "default": {
|
||||
# "BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
# "CONFIG": {
|
||||
# "hosts": [(
|
||||
# "redis://:{passwd}@{host}:{port}/{db}"
|
||||
# .format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
# port=REDIS_PORT, db=REDIS_DB)
|
||||
# )],
|
||||
# },
|
||||
# "ROUTING": "poulpe.routing.channel_routing",
|
||||
# }
|
||||
# }
|
||||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "asgi_redis.RedisChannelLayer",
|
||||
"CONFIG": {
|
||||
"hosts": [(
|
||||
"redis://:{passwd}@{host}:{port}/{db}"
|
||||
.format(passwd=REDIS_PASSWD, host=REDIS_HOST,
|
||||
port=REDIS_PORT, db=REDIS_DB)
|
||||
)],
|
||||
},
|
||||
"ROUTING": "evenementiel.routing.channel_routing",
|
||||
}
|
||||
}
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
|
||||
|
@ -166,55 +148,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.8/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'fr-fr'
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'Europe/Paris'
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
AUTHENTICATION_BACKENDS = (
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
'allauth.account.auth_backends.AuthenticationBackend',
|
||||
)
|
||||
|
||||
SITE_ID = 1
|
||||
|
||||
|
||||
CAS_SERVER_URL = "https://cas.eleves.ens.fr/" # SPI CAS
|
||||
CAS_VERIFY_URL = "https://cas.eleves.ens.fr/"
|
||||
CAS_VERSION = "2"
|
||||
CAS_IGNORE_REFERER = True
|
||||
CAS_LOGIN_MSG = None
|
||||
CAS_REDIRECT_URL = reverse_lazy('shared:home')
|
||||
CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
|
||||
|
||||
LOGIN_URL = reverse_lazy('account_login')
|
||||
LOGOUT_URL = reverse_lazy('account_logout')
|
||||
LOGIN_REDIRECT_URL = reverse_lazy('shared:home')
|
||||
ACCOUNT_HOME_URL = reverse_lazy('shared:home')
|
||||
ACCOUNT_DETAILS_URL = reverse_lazy('shared:home')
|
||||
|
||||
|
||||
SOCIALACCOUNT_PROVIDERS = {
|
||||
# …
|
||||
|
||||
'clipper': {
|
||||
|
||||
# These settings control whether a message containing a link to
|
||||
# disconnect from the CAS server is added when users log out.
|
||||
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT': True,
|
||||
'MESSAGE_SUGGEST_LOGOUT_ON_LOGOUT_LEVEL': messages.INFO,
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
ACCOUNT_ADAPTER = 'shared.allauth_adapter.AccountAdapter'
|
||||
#SOCIALACCOUNT_ADAPTER='allauth_ens.adapter.LongTermClipperAccountAdapter'
|
||||
SOCIALACCOUNT_ADAPTER= 'shared.allauth_adapter.SocialAccountAdapter'
|
||||
|
|
@ -11,12 +11,10 @@ EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
|
|||
DEBUG = True
|
||||
|
||||
# Add some debugging tools
|
||||
INSTALLED_APPS += ["debug_toolbar", ] # NOQA
|
||||
MIDDLEWARE = (
|
||||
[
|
||||
"debug_toolbar.middleware.DebugToolbarMiddleware",
|
||||
]
|
||||
+ MIDDLEWARE # NOQA
|
||||
INSTALLED_APPS += ["debug_toolbar", "debug_panel"] # NOQA
|
||||
MIDDLEWARE_CLASSES = (
|
||||
["debug_panel.middleware.DebugPanelMiddleware"]
|
||||
+ MIDDLEWARE_CLASSES # NOQA
|
||||
)
|
||||
|
||||
|
|
@ -18,6 +18,6 @@ DATABASES = {
|
|||
CHANNEL_LAYERS = {
|
||||
"default": {
|
||||
"BACKEND": "asgiref.inmemory.ChannelLayer",
|
||||
"ROUTING": "poulpe.routing.channel_routing",
|
||||
"ROUTING": "evenementiel.routing.channel_routing",
|
||||
},
|
||||
}
|
|
@ -5,23 +5,11 @@ from django.conf import settings
|
|||
from django.conf.urls import url, include
|
||||
from django.contrib import admin
|
||||
|
||||
#from django_cas_ng import views as django_cas_views
|
||||
from allauth_ens.views import capture_login, capture_logout
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^admin/login/$', capture_login),
|
||||
url(r'^admin/logout/$', capture_logout),
|
||||
url(r'^compte/', include('allauth.urls')),
|
||||
# Admin
|
||||
url(r'^admin/', admin.site.urls),
|
||||
# Apps
|
||||
url(r'^equipment/', include('equipment.urls')),
|
||||
url(r'^event/', include('event.urls')),
|
||||
#url(r'^user/', include('users.urls')),
|
||||
# REST
|
||||
url(r'^user/', include('users.urls')),
|
||||
url(r'^api/', include('api.urls')),
|
||||
# Reste
|
||||
url(r'^', include('shared.urls')),
|
||||
]
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
WSGI config for GestionEvenementiel project.
|
||||
WSGI config for evenementiel project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
|
@ -11,6 +11,6 @@ import os
|
|||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings")
|
||||
|
||||
application = get_wsgi_application()
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'event.apps.EventConfig'
|
|
@ -1,97 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
from .models import Event, Place, ActivityTag, Activity, ActivityTemplate # TODO add me
|
||||
from equipment.models import EquipmentAttribution
|
||||
|
||||
|
||||
class EquipmentAttributionExtraInline(admin.TabularInline):
|
||||
autocomplete_fields = ['equipment', ]
|
||||
model = EquipmentAttribution
|
||||
extra = 0
|
||||
classes = ['collapse']
|
||||
|
||||
|
||||
class EventAdmin(admin.ModelAdmin):
|
||||
list_display = ['title', 'slug', 'beginning_date', 'ending_date']
|
||||
readonly_fields = ['created_by', 'created_at', ]
|
||||
ordering = ['title', 'beginning_date', 'ending_date', ]
|
||||
search_fields = ['title', 'decription', ]
|
||||
list_filter = ['beginning_date', 'ending_date', ]
|
||||
date_hierarchy = 'beginning_date'
|
||||
|
||||
|
||||
class PlaceAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'event', ]
|
||||
ordering = ['name', 'event', ]
|
||||
search_fields = ['name', ]
|
||||
list_filter = ['event', ]
|
||||
|
||||
|
||||
class ActivityTagAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'event', 'is_public', ]
|
||||
ordering = ['name', 'event', 'is_public', ]
|
||||
search_fields = ['name', ]
|
||||
list_filter = ['event', 'is_public', ]
|
||||
|
||||
|
||||
class ActivityTemplateAdmin(admin.ModelAdmin):
|
||||
save_as_continue = True
|
||||
save_on_top = True
|
||||
list_display = ['name', 'title', 'event', 'is_public', ]
|
||||
ordering = ['name', 'title', 'event', 'has_perm', ]
|
||||
search_fields = ['name', 'title', 'description', 'remark', ]
|
||||
list_filter = ['event', 'is_public', 'has_perm', 'tags', ]
|
||||
filter_horizontal = ['tags', 'places', ]
|
||||
fieldsets = (
|
||||
('Identifiant', {
|
||||
'fields': ('name', ),
|
||||
}),
|
||||
('Général', {
|
||||
'fields': ('event', 'title', 'is_public', 'places', ),
|
||||
'description': "Tous ces champs sont héritables (Sauf Évènement)",
|
||||
}),
|
||||
('Permanences', {
|
||||
'fields': ('has_perm', ('min_perm', 'max_perm', ), ),
|
||||
'classes': ('collapse',),
|
||||
'description': "Tous ces champs sont héritables",
|
||||
}),
|
||||
('Descriptions', {
|
||||
'fields': ('description', 'tags', 'remarks', ),
|
||||
'classes': ('collapse',),
|
||||
'description': "Tous ces champs sont héritables",
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
class ActivityAdmin(admin.ModelAdmin):
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_display = ['title', 'event', 'is_public', 'parent', ]
|
||||
ordering = ['title', 'event', 'has_perm', 'parent', ]
|
||||
search_fields = ['title', 'description', 'remark', ]
|
||||
list_filter = ['event', 'is_public', 'has_perm', 'tags', ]
|
||||
filter_horizontal = ['tags', 'places', 'staff', ]
|
||||
inlines = [EquipmentAttributionExtraInline, ]
|
||||
fieldsets = (
|
||||
('Général', {
|
||||
'fields': ('event', 'parent', 'title', 'is_public', 'beginning', 'end', 'places', ),
|
||||
'description': "Tous ces champs sont héritables (sauf parent et Évènement)",
|
||||
}),
|
||||
('Permanences', {
|
||||
'fields': ('has_perm', ('min_perm', 'max_perm', ), 'staff', ),
|
||||
'classes': ('wide',),
|
||||
'description': "Tous ces champs sont héritables (sauf les gens en perm)",
|
||||
}),
|
||||
('Descriptions', {
|
||||
'fields': ('description', 'tags', 'remarks', ),
|
||||
'classes': ('collapse',),
|
||||
'description': "Tous ces champs sont héritables",
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
admin.site.register(Event, EventAdmin)
|
||||
admin.site.register(Place, PlaceAdmin)
|
||||
admin.site.register(ActivityTag, ActivityTagAdmin)
|
||||
admin.site.register(ActivityTemplate, ActivityTemplateAdmin)
|
||||
admin.site.register(Activity, ActivityAdmin)
|
||||
# Register your models here.
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class EventConfig(AppConfig):
|
||||
name = 'event'
|
||||
verbose_name = _("Évènement")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.15 on 2018-08-06 07:51
|
||||
# Generated by Django 1.11.4 on 2017-08-12 12:47
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.conf import settings
|
||||
|
@ -74,8 +74,8 @@ class Migration(migrations.Migration):
|
|||
('slug', models.SlugField(help_text="Seulement des lettres, des chiffres ou les caractères '_' ou '-'.", unique=True, verbose_name='identificateur')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='date de création')),
|
||||
('description', models.TextField(verbose_name='description')),
|
||||
('beginning_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de début')),
|
||||
('ending_date', models.DateTimeField(help_text="date publique de l'évènement", verbose_name='date de fin')),
|
||||
('beginning_date', models.DateTimeField(verbose_name='date de début')),
|
||||
('ending_date', models.DateTimeField(verbose_name='date de fin')),
|
||||
('created_by', models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_events', to=settings.AUTH_USER_MODEL, verbose_name='créé par')),
|
||||
],
|
||||
options={
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-08-20 15:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('event', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='activitytemplate',
|
||||
name='name',
|
||||
field=models.CharField(default='change_me!', help_text='Ne sera pas affiché', max_length=200, verbose_name='Nom du template'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activitytemplate',
|
||||
name='event',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'),
|
||||
),
|
||||
]
|
111
event/models.py
111
event/models.py
|
@ -1,5 +1,5 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.core.exceptions import FieldDoesNotExist, FieldError, ValidationError
|
||||
from django.core.exceptions import FieldDoesNotExist, FieldError
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
@ -123,6 +123,7 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model):
|
|||
Event,
|
||||
verbose_name=_("évènement"),
|
||||
on_delete=models.CASCADE,
|
||||
editable=False,
|
||||
)
|
||||
is_public = models.NullBooleanField(
|
||||
_("est public"),
|
||||
|
@ -166,34 +167,12 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model):
|
|||
|
||||
|
||||
class ActivityTemplate(AbstractActivityTemplate):
|
||||
name = models.CharField(
|
||||
_("Nom du template"),
|
||||
max_length=200,
|
||||
help_text=_("Ne sera pas affiché"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("template activité")
|
||||
verbose_name_plural = _("templates activité")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def clean(self):
|
||||
errors = []
|
||||
|
||||
# On clean les nombre de permanents
|
||||
if not self.has_perm:
|
||||
self.max_perm = None
|
||||
self.min_perm = None
|
||||
else:
|
||||
if self.min_perm > self.max_perm:
|
||||
errors.append(ValidationError(
|
||||
_("Nombres de permanents incompatibles"),
|
||||
code='wrong-nb-perm',
|
||||
))
|
||||
if errors != []:
|
||||
raise ValidationError(errors)
|
||||
return self.title
|
||||
|
||||
|
||||
class Activity(AbstractActivityTemplate):
|
||||
|
@ -214,86 +193,6 @@ class Activity(AbstractActivityTemplate):
|
|||
beginning = models.DateTimeField(_("heure de début"))
|
||||
end = models.DateTimeField(_("heure de fin"))
|
||||
|
||||
def clean(self):
|
||||
errors = []
|
||||
|
||||
# On clean les nombre de permanents
|
||||
if not self.get_herited('has_perm'):
|
||||
self.max_perm = None
|
||||
self.min_perm = None
|
||||
else:
|
||||
if self.get_herited('min_perm') > self.get_herited('max_perm'):
|
||||
errors.append(ValidationError(
|
||||
_("Nombres de permanents incompatibles"),
|
||||
code='wrong-nb-perm',
|
||||
))
|
||||
|
||||
# On valide l'héritage
|
||||
for f in self._meta.get_fields():
|
||||
try:
|
||||
# On réccupère le field du parent
|
||||
attrname = f.name
|
||||
tpl_field = ActivityTemplate._meta.get_field(attrname)
|
||||
# Peut-être que ce n'est pas un field
|
||||
# concerné par l'héritage
|
||||
except FieldDoesNotExist:
|
||||
continue
|
||||
|
||||
# Y'a certains champs dont on se moque
|
||||
if attrname in ['id', 'staff', 'tags', ]:
|
||||
continue
|
||||
|
||||
# C'est plus compliqué que ça pour les nb_perm
|
||||
if attrname in ['max_perm', 'min_perm', ]:
|
||||
if not self.get_herited('has_perm'):
|
||||
continue
|
||||
|
||||
|
||||
# On a un Many to Many, on lit différement
|
||||
if tpl_field.many_to_many:
|
||||
pass
|
||||
# # On a pas spécifié
|
||||
# if not value.exists():
|
||||
# # On a pas de parent
|
||||
# if self.parent is None:
|
||||
# errors.append(ValidationError(
|
||||
# _("N'hérite pas d'un template, spécifier le champs : %(attr)s"),
|
||||
# code='bad-overriding',
|
||||
# params={'attr': f.verbose_name},
|
||||
# ))
|
||||
# else:
|
||||
# pvalue = getattr(self.parent, attrname)
|
||||
# # On a un parent qui ne dit rien
|
||||
# if not pvalue.exists():
|
||||
# errors.append(ValidationError(
|
||||
# _("Champs non précisé chez le parent, spécifier : %(attr)s"),
|
||||
# code='bad-overriding',
|
||||
# params={'attr': f.verbose_name},
|
||||
# ))
|
||||
else:
|
||||
value = getattr(self, attrname)
|
||||
# On a pas spécifié
|
||||
if value is None:
|
||||
# On a pas de parent
|
||||
if self.parent is None:
|
||||
errors.append(ValidationError(
|
||||
_("N'hérite pas d'un template, spécifier le champs : %(attr)s"),
|
||||
code='bad-overriding',
|
||||
params={'attr': f.verbose_name},
|
||||
))
|
||||
else:
|
||||
pvalue = getattr(self.parent, attrname)
|
||||
# On a un parent qui ne dit rien
|
||||
if pvalue is None:
|
||||
errors.append(ValidationError(
|
||||
_("Champs non précisé chez le parent, spécifier : %(attr)s"),
|
||||
code='bad-overriding',
|
||||
params={'attr': f.verbose_name},
|
||||
))
|
||||
if errors != []:
|
||||
raise ValidationError(errors)
|
||||
|
||||
|
||||
def get_herited(self, attrname):
|
||||
try:
|
||||
tpl_field = ActivityTemplate._meta.get_field(attrname)
|
||||
|
@ -308,9 +207,9 @@ class Activity(AbstractActivityTemplate):
|
|||
if tpl_field.many_to_many:
|
||||
if value.exists():
|
||||
return value
|
||||
elif self.parent is not None:
|
||||
else:
|
||||
return getattr(self.parent, attrname)
|
||||
elif value is None and self.parent is not None:
|
||||
elif value is None:
|
||||
return getattr(self.parent, attrname)
|
||||
else:
|
||||
return value
|
||||
|
|
|
@ -1,373 +0,0 @@
|
|||
/* Calendar */
|
||||
|
||||
#cal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
|
||||
min-height: 80vh;
|
||||
display: block;
|
||||
position: relative;
|
||||
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
#cal-container,
|
||||
#cal-container * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Time slots */
|
||||
|
||||
#cal-container .cal-time-slot-container {
|
||||
display: grid;
|
||||
grid-template-rows: 30px auto;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% + 30px + 10px);
|
||||
padding: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot {
|
||||
border-right: 1px solid #EEE;
|
||||
background-color: #FAFAFA;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot:nth-child(even) {
|
||||
background-color: #F4F4F4;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot-hour {
|
||||
padding: 0 0 0 calc(100% - 1.4rem + 7px);
|
||||
background-color: #F9F9F9;/* #3D3D3D; */
|
||||
border-bottom: 1px solid #AAA;
|
||||
color: #333;
|
||||
font-size: 1.4rem;
|
||||
line-height: 30px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot-hour:nth-child(even) {
|
||||
background-color: #FCFCFC;/* #464646; */
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot-hour:first-child {
|
||||
color: transparent;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot-hour.cal-last-hour {
|
||||
padding: 0;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot.cal-last-hour,
|
||||
#cal-container .cal-time-slot-hour.cal-last-hour {
|
||||
border-right: 1px solid #AAA;
|
||||
}
|
||||
|
||||
#cal-container .cal-time-slot.cal-first-hour,
|
||||
#cal-container .cal-time-slot-hour.cal-first-hour {
|
||||
border-left: 1px solid #AAA;
|
||||
}
|
||||
|
||||
/* Events */
|
||||
|
||||
#cal-container .cal-event-container {
|
||||
display: grid;
|
||||
/* grid-template-columns: repeat(24, 1fr); */
|
||||
/* grid-template-rows: repeat(12, auto); */
|
||||
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
#cal-container .cal-event {
|
||||
position: relative;
|
||||
height: 80px;
|
||||
margin: 2px 0;
|
||||
/* padding: 5px; */
|
||||
/* background-color: #EFEFEF; */
|
||||
border-radius: 3px;
|
||||
/* border: 1px solid #CCC; */
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
/* z-index: 500; */
|
||||
transition: 50ms ease-in;
|
||||
}
|
||||
|
||||
#cal-container .cal-event:hover {
|
||||
/* background-color: #FEDDDD; */
|
||||
}
|
||||
|
||||
#cal-container .cal-event > * {
|
||||
display: none;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
#cal-container .cal-event > .cal-event-name,
|
||||
#cal-container .cal-event > .cal-event-location,
|
||||
#cal-container .cal-event > .cal-event-perm-count {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#cal-container .cal-event > .cal-event-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#cal-container .cal-event > .cal-event-location {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#cal-container .cal-event > .cal-event-perm-count {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#cal-container .cal-event:not(.cal-event-subscribed) > .cal-event-perm-count.cal-perms-missing {
|
||||
width: calc(100% - 10px);
|
||||
right: auto;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
background-color: #FFF;
|
||||
border: 2px solid #E44;
|
||||
color: #E44;
|
||||
font-weight: bold;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#cal-container .cal-event.cal-event-subscribed {
|
||||
border-width: 3px;
|
||||
border-color: #000;
|
||||
}
|
||||
|
||||
#cal-container .cal-event.cal-event-subscribed::after {
|
||||
content: "✔";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
padding: 1px;
|
||||
color: #fff;
|
||||
background-color: #000;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
||||
|
||||
/* Event details popup */
|
||||
|
||||
#cal-container .cal-event-details {
|
||||
position: absolute;
|
||||
min-height: 100px;
|
||||
/* min-width: 40%; */
|
||||
max-width: 80%;
|
||||
padding: 20px;
|
||||
background-color: #333;
|
||||
color: #FFF;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 15px 50px rgba(0, 0, 0, 0.6);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details:after {
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-bottom-color: #333;
|
||||
border-width: 20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details.above-event:after {
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border-top-color: #333;
|
||||
border-width: 20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details * {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-close-button {
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
margin: 0;
|
||||
padding: 5px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
font-size: 1.6rem;
|
||||
color: #BBB;
|
||||
transition: 100ms ease-out;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-close-button:hover {
|
||||
background-color: #484848;
|
||||
color: #EFEFEF;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details a,
|
||||
#cal-container .cal-event-details a:hover {
|
||||
color: #FFF;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-name > h3 {
|
||||
margin: 0 0 25px 26px;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
font-size: 2.5rem;
|
||||
text-transform: uppercase;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-name > h3::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 0 0 10px;
|
||||
vertical-align: middle;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAA90lEQVQoz2WRsS6DURiGn/yaaNqBySpGicFg6iJcAYuuNBaJpJEg6gLcADFIdWR0BZZKS4SBRTe3YFf/Y/j/c3LUe4Zz3vd78+U734uUJ7Ptg7k68NDpoIdyU9V3TzzwRdXt1HCq9pwR510W616oZ8Gwoe6KWFN1ScStogui3sRJ9uxYLd/n6hTuaCy3XHfNWuR6hM+OojBQdSXyJ0cZizwSsMo3kEc+ZCEjxZgxE8j4oBFZhRww8gaff4fEL3UuGfK4uG5L4VLVfvrNCrDJHfd0gSrXQB2AJvu0+Cm8HbXnbGw9seqwWH2z65Wv/8MKcfdVHaZx/wKtOg5kifzQhwAAAABJRU5ErkJggg==);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-name:hover > h3 {
|
||||
margin-right: 26px;
|
||||
margin-bottom: 10px;
|
||||
background-color: #484848;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-name:hover > h3::after {
|
||||
content: "Cliquez pour afficher les détails.";
|
||||
display: block;
|
||||
width: auto;
|
||||
height: 15px;
|
||||
padding: 2px 0 0 0;
|
||||
font-size: 1.2rem;
|
||||
background-image: none;
|
||||
color: #DDD;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-name:hover + .cal-detail-close-button {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details table {
|
||||
margin: 0 auto;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details td.cal-detail-label {
|
||||
padding: 0 10px 10px 0;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details td.cal-detail-value {
|
||||
padding: 0 0 10px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #DFDFDF;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-title {
|
||||
display: block;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count {
|
||||
margin: 0 10px 0 0;
|
||||
font-size: 2.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-missing {
|
||||
color: #E44;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-count.cal-perms-full {
|
||||
color: #393;
|
||||
}
|
||||
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-subscription-switch {
|
||||
margin: 0 0 0 10px;
|
||||
padding: 10px;
|
||||
font-size: 1.8rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-perm-area .cal-detail-perm-nb-missing-perms {
|
||||
margin: 20px 0 0 0;
|
||||
padding: 5px;
|
||||
background-color: #FFF;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: #E44;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-description {
|
||||
margin: 20px 0 20px 0;
|
||||
color: #DDD;
|
||||
font-size: 1.8rem;
|
||||
font-style: italic;
|
||||
text-align: justify;
|
||||
line-height: 120%;
|
||||
}
|
||||
|
||||
#cal-container .cal-event-details .cal-detail-tag {
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
padding: 5px;
|
||||
border: 1px solid #DDD;
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
/* Tipso Bubble Styles */
|
||||
.tipso_bubble, .tipso_bubble > .tipso_arrow{
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.tipso_bubble {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.tipso_style{
|
||||
/* cursor: help; */
|
||||
border-bottom: 1px dotted;
|
||||
}
|
||||
.tipso_title {
|
||||
padding: 3px 0;
|
||||
border-radius: 6px 6px 0 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
.tipso_content {
|
||||
word-wrap: break-word;
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
/* Tipso Bubble size classes - Similar to Foundation's syntax*/
|
||||
.tipso_bubble.tiny {
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
.tipso_bubble.small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.tipso_bubble.default {
|
||||
font-size: 1rem;
|
||||
}
|
||||
.tipso_bubble.large {
|
||||
font-size: 1.2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tipso_bubble.cal_small {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
/* Tipso Bubble Div */
|
||||
.tipso_bubble > .tipso_arrow{
|
||||
position: absolute;
|
||||
width: 0; height: 0;
|
||||
border: 8px solid;
|
||||
pointer-events: none;
|
||||
}
|
||||
.tipso_bubble.top > .tipso_arrow {
|
||||
border-top-color: #000;
|
||||
border-right-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
}
|
||||
.tipso_bubble.bottom > .tipso_arrow {
|
||||
border-bottom-color: #000;
|
||||
border-right-color: transparent;
|
||||
border-left-color: transparent;
|
||||
border-top-color: transparent;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
margin-left: -8px;
|
||||
}
|
||||
.tipso_bubble.left > .tipso_arrow {
|
||||
border-left-color: #000;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-right-color: transparent;
|
||||
top: 50%;
|
||||
left: 100%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
.tipso_bubble.right > .tipso_arrow {
|
||||
border-right-color: #000;
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
border-left-color: transparent;
|
||||
top: 50%;
|
||||
right: 100%;
|
||||
margin-top: -8px;
|
||||
}
|
||||
|
||||
.tipso_bubble .top_right_corner,
|
||||
.tipso_bubble.top_right_corner {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
.tipso_bubble .bottom_right_corner,
|
||||
.tipso_bubble.bottom_right_corner {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
.tipso_bubble .top_left_corner,
|
||||
.tipso_bubble.top_left_corner {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.tipso_bubble .bottom_left_corner,
|
||||
.tipso_bubble.bottom_left_corner {
|
||||
border-top-right-radius: 0;
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,18 +0,0 @@
|
|||
$(function(){
|
||||
function initEnrolment(elt) {
|
||||
elt = $(elt);
|
||||
elt.find("form.enrolment").on("submit", function() {
|
||||
elt.addClass("sending-request");
|
||||
var form = this;
|
||||
var url = form.action + "?ajax";
|
||||
$.post(url, $(form).serialize(), function(data) {
|
||||
elt.html(data);
|
||||
elt.removeClass("sending-request");
|
||||
initEnrolment(elt);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
$.each($(".activity-summary"), function(i, item) { initEnrolment(item) });
|
||||
});
|
|
@ -1,84 +0,0 @@
|
|||
// Interval graph coloring algorithm, by Twal
|
||||
|
||||
class IntervalColoration {
|
||||
constructor (intervals) {
|
||||
this.intervals = intervals;
|
||||
this.n = this.intervals.length;
|
||||
this.computeInterferenceGraph();
|
||||
this.computePEO();
|
||||
this.computeColoration();
|
||||
}
|
||||
|
||||
computeInterferenceGraph() {
|
||||
this.adj = new Array(this.n);
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
this.adj[i] = [];
|
||||
}
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
for (let j = 0; j < i; ++j) {
|
||||
let inti = this.intervals[i];
|
||||
let intj = this.intervals[j];
|
||||
if (inti[0] < intj[1] && intj[0] < inti[1]) {
|
||||
this.adj[i].push(j);
|
||||
this.adj[j].push(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Perfect elimination order using Maximum Cardinality Search
|
||||
//Runs in O(n^2), could be optimized in O(n log n)
|
||||
computePEO() {
|
||||
let marked = new Array(this.n);
|
||||
let nbMarkedNeighbor = new Array(this.n);
|
||||
this.perm = new Array(this.n);
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
marked[i] = false;
|
||||
nbMarkedNeighbor[i] = 0;
|
||||
}
|
||||
for (let k = this.n-1; k >= 0; --k) {
|
||||
let maxi = -1;
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
if (!marked[i] && (maxi == -1 || nbMarkedNeighbor[i] >= nbMarkedNeighbor[maxi])) {
|
||||
maxi = i;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < this.adj[maxi].length; ++i) {
|
||||
nbMarkedNeighbor[this.adj[maxi][i]] += 1;
|
||||
}
|
||||
this.perm[maxi] = k;
|
||||
marked[maxi] = true;
|
||||
}
|
||||
// console.log(this.perm);
|
||||
}
|
||||
|
||||
computeColoration() {
|
||||
this.colors = new Array(this.n);
|
||||
let isColorUsed = new Array(this.n);
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
this.colors[i] = -1;
|
||||
isColorUsed[i] = false;
|
||||
}
|
||||
for (let i = 0; i < this.n; ++i) {
|
||||
let ind = this.perm[i];
|
||||
for (let j = 0; j < this.adj[ind].length; ++j) {
|
||||
let neigh = this.adj[ind][j];
|
||||
if (this.colors[neigh] >= 0) {
|
||||
isColorUsed[this.colors[neigh]] = true;
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < this.n; ++j) {
|
||||
if (!isColorUsed[j]) {
|
||||
this.colors[ind] = j;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (let j = 0; j < this.adj[ind].length; ++j) {
|
||||
let neigh = this.adj[ind][j];
|
||||
if (this.colors[neigh] >= 0) {
|
||||
isColorUsed[this.colors[neigh]] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
1
event/static/js/tipso.min.js
vendored
1
event/static/js/tipso.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,52 +0,0 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% load i18n staticfiles event_tags %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}{% trans "Activity" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ activity.title}}
|
||||
{% if user.is_staff %}
|
||||
<a class='glyphicon glyphicon-cog pull-right' href='{% url "admin:event_activity_change" activity.id %}'></a>
|
||||
{% endif %}
|
||||
</h1>
|
||||
{% include "event/activity_summary.html" with activity=activity %}
|
||||
|
||||
<h2>Description</h2>
|
||||
<p><strong>Description</strong>{{activity.description|default:" - "}}</p>
|
||||
<p><strong>Remarque (staff)</strong>{{activity.remark|default:" - "}}</p>
|
||||
<button class="collapsible active"><h3>Matériel</h3></button>
|
||||
<div class="content fluid">
|
||||
<table class="table table-responsive table-striped">
|
||||
<tr>
|
||||
<th>Matériel</th>
|
||||
<th>Quantité</th>
|
||||
<th>Propriétaire</th>
|
||||
<th>Remarque</th>
|
||||
</tr>
|
||||
{% for att in attributions %}
|
||||
<tr>
|
||||
<td><a href="{% url 'equipment:detail' att.equipment.id %}">
|
||||
{{ att.equipment }}</a></td>
|
||||
<td>{{ att.amount }}</td>
|
||||
<td>{{ att.equipment.owner }}</td>
|
||||
<td>{{ att.remark }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
<div class="text">
|
||||
Du
|
||||
<strong>
|
||||
{{ activity.beginning | date:"l d F Y H:i" }}
|
||||
</strong>
|
||||
au
|
||||
<strong>
|
||||
{{ activity.end | date:"l d F Y H:i" }}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,79 +0,0 @@
|
|||
{% load i18n event_tags %}
|
||||
{% with activity|get_herited:'has_perm' as has_perm %}
|
||||
<table class="table table-responsive table-striped">
|
||||
<tr>
|
||||
<td>
|
||||
<span class="glyphicon glyphicon-tree-deciduous"></span>
|
||||
{% with activity|get_herited:'places' as places %}
|
||||
{% if places.all %}
|
||||
<span>{{ places.all |join:", "}}</span>
|
||||
{% else %}
|
||||
<span> - </span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>public</strong>
|
||||
:
|
||||
{% with activity|get_herited:'is_public' as is_public %}
|
||||
<span class="glyphicon {{ is_public|yesno:"yes glyphicon-ok-sign, no glyphicon-remove-sign, dunno glyphicon-question-sign"}}"></span>
|
||||
{% endwith %}
|
||||
</td>
|
||||
<tr>
|
||||
</tr>
|
||||
<td>
|
||||
<span class="glyphicon glyphicon-duplicate"></span>
|
||||
{% if activity.parent %}
|
||||
<span>{{ activity.parent}}</span>
|
||||
{% else %}
|
||||
<span> - </span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
<strong>perm</strong>
|
||||
:
|
||||
<span class="glyphicon {{ has_perm|yesno:"yes glyphicon-ok-sign, no glyphicon-remove-sign, dunno glyphicon-question-sign"}}"></span>
|
||||
</td>
|
||||
<tr>
|
||||
</tr>
|
||||
<td>
|
||||
<span class="glyphicon glyphicon-tag"></span>
|
||||
{% with activity|get_herited:'tags' as tags %}
|
||||
{% if tags.all %}
|
||||
<span>{{ tags.all |join:", "}}</span>
|
||||
{% else %}
|
||||
<span> - </span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</td>
|
||||
<td>
|
||||
{% if has_perm %}
|
||||
{{ activity|get_herited:'min_perm' }}
|
||||
≤
|
||||
<strong>{{ activity.staff.count }}</strong>
|
||||
≤
|
||||
{{ activity|get_herited:'max_perm' }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{% if has_perm %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-8">
|
||||
<strong>En perm : </strong>
|
||||
{% with activity.staff.all as staff %}
|
||||
{% if staff %}
|
||||
<span>{{ staff |join:", "}}</span>
|
||||
{% else %}
|
||||
<span> - </span>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
{% enrol_btn activity request.user %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
|
@ -1,2 +1,25 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block sidenav %}
|
||||
<div class="centered">
|
||||
<h5 class="centered banner-text">La Nuit 2017</h5>
|
||||
</div>
|
||||
|
||||
<li>
|
||||
<a href="index.html">
|
||||
<i class="fa fa-dashboard"></i>
|
||||
<span>Looool</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="sub-menu">
|
||||
<a href="javascript:;" >
|
||||
<i class="fa fa-desktop"></i>
|
||||
<span>Prout</span>
|
||||
</a>
|
||||
<ul class="sub">
|
||||
<li><a href="general.html">Lolilol</a></li>
|
||||
<li><a href="buttons.html">Lorem</a></li>
|
||||
<li><a href="panels.html">Ipsum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
{% extends "shared/fluid.html" %}
|
||||
{% load i18n staticfiles event_tags %}
|
||||
|
||||
{% block extra_css %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static "css/tipso.css" %}">
|
||||
<link rel="stylesheet" href="{% static "css/calendar.css" %}">
|
||||
|
||||
<style>
|
||||
#cal-toggle-unsubscribed-events-display {
|
||||
display: block;
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
left: 30px;
|
||||
border-bottom: 2px solid rgb(150, 50, 50);
|
||||
font-size: 1.6rem;
|
||||
font-weight: bold;
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.4),
|
||||
0 1px 1px rgba(150, 50, 50, 0.7);
|
||||
text-shadow: 0 0 7px rgb(150, 50, 50);
|
||||
z-index: 5000;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="{% static "js/tipso.min.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "js/interval_coloration.js" %}"></script>
|
||||
<script type="text/javascript" src="{% static "js/calendar.js" %}"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(() => {
|
||||
let calendar = new Calendar({
|
||||
startDate: new Date(2018, 10, 30, 8),
|
||||
endDate: new Date(2018, 11, 2, 6),
|
||||
eventDetailURLFormat: "https://cof.ens.fr/poulpe/event/activity/999999",
|
||||
subscriptionURLFormat: "{% url "event:enrol_activity" 999999 %}?ajax=json",
|
||||
csrfToken: $(".planning [name=csrfmiddlewaretoken]").val(),
|
||||
groupEventsByLocation: true
|
||||
});
|
||||
|
||||
// TODO: move this elsewhere
|
||||
// Button to switch between:
|
||||
// - displaying all events (default);
|
||||
// - only displaying events for which the current user is enroled.
|
||||
|
||||
// Create the button
|
||||
let toggleUnsubscribedEventDisplayButton = $("<button>")
|
||||
.attr("type", "button")
|
||||
.attr("id", "cal-toggle-unsubscribed-events-display")
|
||||
.addClass("btn btn-primary")
|
||||
.appendTo(calendar.containerNode);
|
||||
|
||||
// Set/update its label
|
||||
function updateToggleButtonLabel () {
|
||||
if (calendar.onlyDisplaySubscribedEvents) {
|
||||
toggleUnsubscribedEventDisplayButton.html("Afficher toutes les activités");
|
||||
}
|
||||
else {
|
||||
toggleUnsubscribedEventDisplayButton.html("Afficher seulement mes permanences");
|
||||
}
|
||||
}
|
||||
|
||||
updateToggleButtonLabel();
|
||||
|
||||
// Switch between display modes on click
|
||||
toggleUnsubscribedEventDisplayButton.on("click", () => {
|
||||
calendar.toggleEventsNotSubscribedByUser();
|
||||
updateToggleButtonLabel();
|
||||
});
|
||||
|
||||
|
||||
// DEBUG: js console helpers, to be removed
|
||||
console.log(calendar);
|
||||
window["cal"] = calendar;
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="planning">
|
||||
{% csrf_token %}
|
||||
{% regroup activities by beginning|date:"Y-m-d" as days_list %}
|
||||
<div class="content fluid" id="cal-container">
|
||||
{% for day in days_list %}
|
||||
{% for activity in day.list %}
|
||||
<div class="cal-event">
|
||||
<span class="cal-event-id">{{ activity.id }}</span>
|
||||
<span class="cal-event-name">{{ activity|get_herited:'title' }}</span>
|
||||
<span class="cal-event-start-date">{{ activity.beginning | date:"j/m/Y H:i" }}</span>
|
||||
<span class="cal-event-end-date">{{ activity.end | date:"j/m/Y H:i" }}</span>
|
||||
{% with activity|get_herited:'places' as places %}
|
||||
<span class="cal-event-location">{{ places.all | join:", " }}</span>
|
||||
{% endwith %}
|
||||
<span class="cal-event-description">{{ activity.description }}</span>
|
||||
<span class="cal-event-url">{% url "event:activity" activity.id %}</span>
|
||||
|
||||
{% if activity|get_herited:'has_perm' %}
|
||||
<span class="cal-event-has-perms">1</span>
|
||||
<span class="cal-event-min-nb-perms">{{ activity|get_herited:'min_perm' }}</span>
|
||||
<span class="cal-event-max-nb-perms">{{ activity|get_herited:'max_perm' }}</span>
|
||||
<span class="cal-event-nb-perms">{{ activity.staff.count }}</span>
|
||||
<span class="cal-event-subscribed">{% is_enrolled activity request.user %}</span>
|
||||
{% endif %}
|
||||
|
||||
{% with activity|get_herited:'tags' as tags %}
|
||||
{% for tag in tags.all %}
|
||||
<span class="cal-event-tag">{{ tag.name }}</span>
|
||||
{% endfor %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,101 +0,0 @@
|
|||
{% extends "shared/base.html" %}
|
||||
{% load i18n staticfiles event_tags %}
|
||||
|
||||
{% block title %}{% trans "Évènement" %}{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
{{ block.super }}
|
||||
<script type="text/javascript" src="{% static "js/enrol_event.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ event.title}}
|
||||
{% if perms.event.event_can_change and user.is_staff %}
|
||||
<a class='glyphicon glyphicon-cog pull-right' href='{% url "admin:event_event_change" event.id %}'></a>
|
||||
{% endif %}
|
||||
</h1>
|
||||
<p>{{ event.description }}</p>
|
||||
<h2>Boîte à outils</h2>
|
||||
<div class="module-list">
|
||||
<a href="#TODO" class="module">
|
||||
<span class="glyphicon glyphicon-duplicate"></span>
|
||||
Templates d'activité
|
||||
</a>
|
||||
<a href="#TODO" class="module">
|
||||
<span class="glyphicon glyphicon-tag"></span>
|
||||
Tags spécifiques
|
||||
</a>
|
||||
<a href="#todo" class="module">
|
||||
<span class="glyphicon glyphicon-tree-deciduous"></span>
|
||||
lieux spécifiques
|
||||
</a>
|
||||
<a href="{% url "event:calendar" event.slug %}" class="module">
|
||||
Calendrier
|
||||
</a>
|
||||
{% if staffuser %}
|
||||
<a href="{% url "event:event" event.slug %}" class="module">
|
||||
Toutes les activités
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{% url "event:event-staff" event.slug user.username %}" class="module">
|
||||
Mes perms
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if staffuser %}
|
||||
<h2>Perms de {{ staffuser.first_name }} {{ staffuser.last_name }}</h2>
|
||||
{% else %}
|
||||
<h2>Planning</h2>
|
||||
{% endif %}
|
||||
|
||||
<div class="planning">
|
||||
{% regroup activities by beginning|date:"Y-m-d" as days_list %}
|
||||
{% for day in days_list %}
|
||||
{% with day.list|first as f_act %}
|
||||
<button class="collapsible active"><h3>{{ f_act.beginning|date:"l d F" }}</h3></button>
|
||||
<div class="content fluid">
|
||||
{% endwith %}
|
||||
{% for activity in day.list %}
|
||||
<div class="{% cycle "" "inverted" %} activity">
|
||||
<div class="activity-title">
|
||||
<h4>
|
||||
{% if perms.event.activity_can_change and user.is_staff %}
|
||||
<a class='glyphicon glyphicon-cog' href='{% url "admin:event_activity_change" activity.id %}'></a>
|
||||
{% endif %}
|
||||
<a href="{% url "event:activity" activity.id %}">
|
||||
{{ activity|get_herited:'title' }}
|
||||
</a>
|
||||
</h4>
|
||||
<span class="pull-right">
|
||||
de <strong>{{ activity.beginning | time:"H:i" }}</strong>
|
||||
à <strong>{{ activity.end| time:"H:i" }}</strong>
|
||||
</span>
|
||||
<div class="activity-summary">
|
||||
{% include "event/activity_summary.html" with activity=activity %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block aside %}
|
||||
<div class="heading separator">
|
||||
{{ activities.count }} <span class="sub">activité{{ activities.count|pluralize }}</span>
|
||||
</div>
|
||||
<div class="text inverted">
|
||||
<p>Créé le {{ event.created_at | date:"l d F Y à H:i" }} par {{ event.created_by }}</p>
|
||||
<p>
|
||||
Du
|
||||
<strong>
|
||||
{{ event.beginning_date | date:"l d F Y H:i" }}
|
||||
</strong>
|
||||
au
|
||||
<strong>
|
||||
{{ event.ending_date | date:"l d F Y H:i" }}
|
||||
</strong>
|
||||
</p>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,6 +0,0 @@
|
|||
{% load i18n %}
|
||||
<form method="POST" action="{% url "event:enrol_activity" activity.pk %}" class="enrolment {{ enrolled|yesno:"enrolled,unenrolled" }}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="goal" value="{{ enrolled|yesno:"unenrol,enrol" }}" />
|
||||
{{ enrolled|yesno:_("Inscrit,") }} <input type="submit" value="{{ enrolled|yesno:_("Se désinscrire,S'inscrire") }}" class="btn btn-warning"/>
|
||||
</form>
|
|
@ -1,21 +0,0 @@
|
|||
from django import template
|
||||
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter()
|
||||
def get_herited(activity, attrname):
|
||||
return activity.get_herited(attrname)
|
||||
|
||||
@register.inclusion_tag("event/tags/enrol_btn.html")
|
||||
def enrol_btn(activity, user):
|
||||
return {
|
||||
"enrolled": activity.staff.filter(id=user.id).exists(),
|
||||
"activity": activity,
|
||||
}
|
||||
|
||||
@register.simple_tag
|
||||
def is_enrolled(activity, user):
|
||||
user_is_enrolled = activity.staff.filter(id=user.id).exists()
|
||||
return "1" if user_is_enrolled else "0"
|
|
@ -1,14 +1,7 @@
|
|||
from django.conf.urls import url
|
||||
from event.views import Index, EventView, EventViewStaff, ActivityView, EnrolActivityView, EventCalendar
|
||||
from event.views import Index
|
||||
|
||||
app_name = 'event'
|
||||
urlpatterns = [
|
||||
# url(r'^$', Index.as_view(), name='index'),
|
||||
url(r'^(?P<slug>[-\w]+)/$', EventView.as_view(), name='event'),
|
||||
url(r'^(?P<slug>[-\w]+)/s/(?P<username>[-\w]+)/$', EventViewStaff.as_view(), name='event-staff'),
|
||||
url(r'^(?P<slug>[-\w]+)/calendar/$', EventCalendar.as_view(), name='calendar'),
|
||||
url(r'^activity/(?P<pk>[0-9]+)/$', ActivityView.as_view(),
|
||||
name='activity'),
|
||||
url(r'^activity/(?P<pk>[0-9]+)/enrol/$',
|
||||
EnrolActivityView.as_view(), name="enrol_activity"),
|
||||
url(r'^$', Index.as_view(), name='index'),
|
||||
]
|
||||
|
|
|
@ -1,97 +1,5 @@
|
|||
from django.views.generic import TemplateView, DetailView, View
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.http import JsonResponse, HttpResponseRedirect
|
||||
from django.urls import reverse
|
||||
|
||||
from .models import Event, Activity
|
||||
from equipment.models import EquipmentAttribution
|
||||
|
||||
User = get_user_model()
|
||||
from django.views.generic import TemplateView
|
||||
|
||||
|
||||
class Index(TemplateView):
|
||||
template_name = "event/index.html"
|
||||
|
||||
|
||||
class EventCalendar(LoginRequiredMixin, DetailView):
|
||||
model = Event
|
||||
template_name = 'event/calendar.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
event = self.object
|
||||
context['activities'] = (Activity.objects.filter(event=event)
|
||||
.order_by('beginning').prefetch_related(
|
||||
'tags', 'places', 'staff', 'parent'))
|
||||
return context
|
||||
|
||||
|
||||
class EventView(LoginRequiredMixin, DetailView):
|
||||
model = Event
|
||||
template_name = 'event/event.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
event = self.object
|
||||
context['activities'] = (Activity.objects.filter(event=event)
|
||||
.order_by('beginning').prefetch_related(
|
||||
'tags', 'places', 'staff', 'parent'))
|
||||
return context
|
||||
|
||||
|
||||
class EventViewStaff(LoginRequiredMixin, DetailView):
|
||||
model = Event
|
||||
template_name = 'event/event.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
user = User.objects.get(username=self.kwargs['username'])
|
||||
event = self.object
|
||||
context['staffuser'] = user
|
||||
context['activities'] = (user.in_perm_activities
|
||||
.filter(event=event)
|
||||
.order_by('beginning')
|
||||
.prefetch_related(
|
||||
'tags', 'places', 'staff', 'parent')
|
||||
)
|
||||
return context
|
||||
|
||||
|
||||
class ActivityView(LoginRequiredMixin, DetailView):
|
||||
model = Activity
|
||||
template_name = 'event/activity.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
activity = self.object
|
||||
context['attributions'] = (EquipmentAttribution.objects
|
||||
.filter(activity=activity)
|
||||
.prefetch_related('equipment'))
|
||||
return context
|
||||
|
||||
|
||||
class EnrolActivityView(LoginRequiredMixin, View):
|
||||
http_method_names = ['post']
|
||||
|
||||
def post(self, request, pk, *args, **kwargs):
|
||||
activity = get_object_or_404(Activity, id=pk)
|
||||
action = request.POST.get("goal", None)
|
||||
success = True
|
||||
if action == "enrol":
|
||||
activity.staff.add(request.user)
|
||||
elif action == "unenrol":
|
||||
activity.staff.remove(request.user)
|
||||
else:
|
||||
success = False
|
||||
if "ajax" in request.GET:
|
||||
if request.GET["ajax"] == "json":
|
||||
enrols = activity.staff
|
||||
return JsonResponse({
|
||||
"enrolled": enrols.filter(id=request.user.id).exists(),
|
||||
"number": enrols.count(),
|
||||
})
|
||||
return render(request, "event/activity_summary.html",
|
||||
{"activity": activity})
|
||||
return HttpResponseRedirect(reverse("event:activity", kwargs={"pk":pk}))
|
||||
|
|
|
@ -3,7 +3,7 @@ import os
|
|||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "poulpe.settings.devlocal")
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "evenementiel.settings.devlocal")
|
||||
|
||||
from django.core.management import execute_from_command_line
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ DBPASSWD="4KZt3nGPLVeWSvtBZPsd9jdssdJMds78"
|
|||
REDIS_PASSWD="dummy"
|
||||
|
||||
# It is used in quite a few places
|
||||
SETTINGS="poulpe.settings.dev"
|
||||
SETTINGS="evenementiel.settings.dev"
|
||||
|
||||
# Fills a "templated file" with the information specified in the variables above
|
||||
# e.g. every occurrence of {{DBUSER}} in the file will be replaced by the value
|
||||
|
@ -90,9 +90,9 @@ redis-cli -a $REDIS_PASSWD CONFIG REWRITE
|
|||
cd /vagrant
|
||||
|
||||
# Setup the secrets
|
||||
sudo -H -u vagrant cp poulpe/settings/secret_example.py \
|
||||
poulpe/settings/secret.py
|
||||
fill_template poulpe/settings/secret.py
|
||||
sudo -H -u vagrant cp evenementiel/settings/secret_example.py \
|
||||
evenementiel/settings/secret.py
|
||||
fill_template evenementiel/settings/secret.py
|
||||
|
||||
# Run the usual django admin commands
|
||||
function venv_python {
|
||||
|
|
|
@ -10,7 +10,7 @@ TimeoutSec=300
|
|||
WorkingDirectory=/vagrant
|
||||
Environment="DJANGO_SETTINGS_MODULE={{SETTINGS}}"
|
||||
ExecStart=/home/vagrant/venv/bin/daphne -u /srv/GE/GE.sock \
|
||||
poulpe.asgi:channel_layer
|
||||
evenementiel.asgi:channel_layer
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
-r requirements.txt
|
||||
django-debug-toolbar
|
||||
django-debug-panel
|
||||
ipython
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
-r requirements.txt
|
||||
|
||||
# Production specific
|
||||
daphne==1.3.0
|
||||
psycopg2
|
||||
gunicorn
|
|
@ -1,14 +1,14 @@
|
|||
asgi-redis==1.3.0
|
||||
asgiref==1.1.1
|
||||
django-bootstrap3==10.0.1
|
||||
channels==1.1.5
|
||||
Django==2.1
|
||||
django-tables2==2.0.0a2
|
||||
django-filter==2.0.0
|
||||
git+https://git.eleves.ens.fr/klub-dev-ens/django-allauth-ens.git@1.1.3
|
||||
django-bootstrap-form==3.4
|
||||
Django==1.11.*
|
||||
psycopg2
|
||||
asgi-redis
|
||||
Pillow
|
||||
channels
|
||||
django-bootstrap-form==3.2.1
|
||||
django-widget-tweaks
|
||||
djangorestframework==3.6.3
|
||||
drf-nested-routers==0.90.0
|
||||
django-notifications==0.1.dev0
|
||||
django-contrib-comments==1.8.0
|
||||
django-taggit==0.22.2
|
||||
Pillow==5.0.0
|
||||
django-notifications
|
||||
django-contrib-comments
|
||||
|
||||
# Production specific
|
||||
daphne
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
default_app_config = 'shared.apps.SharedConfig'
|
|
@ -1,34 +1,3 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.admin import AdminSite
|
||||
from django.contrib.sites.admin import SiteAdmin
|
||||
from django.contrib.sites.models import Site
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group
|
||||
from django.contrib.auth.admin import UserAdmin, GroupAdmin
|
||||
|
||||
User = get_user_model()
|
||||
|
||||
|
||||
class CustomAdminSite(AdminSite):
|
||||
site_header = "Administration du Poulpe"
|
||||
site_title = "Poulpe"
|
||||
index_title = "Administration"
|
||||
|
||||
def index(self, request, extra_context=None):
|
||||
if extra_context is None:
|
||||
extra_context = {}
|
||||
|
||||
# Move last app to the top of `app_list`.
|
||||
# TODO fournir un bon ordre
|
||||
app_list = self.get_app_list(request)
|
||||
app_list.insert(0, app_list.pop(-1))
|
||||
|
||||
extra_context['app_list'] = app_list
|
||||
return super().index(request, extra_context)
|
||||
|
||||
|
||||
# admin.site = CustomAdminSite(name='admin')
|
||||
admin.site.register(User, UserAdmin)
|
||||
# admin.site.register(Group, GroupAdmin)
|
||||
# admin.site.register(Site, SiteAdmin)
|
||||
# Register your models here.
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
from allauth.account.adapter import DefaultAccountAdapter
|
||||
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
|
||||
|
||||
|
||||
class AccountAdapter(DefaultAccountAdapter):
|
||||
def is_open_for_signup(self, request):
|
||||
return False
|
||||
|
||||
|
||||
class SocialAccountAdapter(DefaultSocialAccountAdapter):
|
||||
def is_open_for_signup(self, request, sociallogin):
|
||||
# sociallogin.account is a SocialAccount instance.
|
||||
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/models.py
|
||||
|
||||
if sociallogin.account.provider == 'clipper':
|
||||
return True
|
||||
|
||||
# It returns AccountAdapter.is_open_for_signup().
|
||||
# See https://github.com/pennersr/django-allauth/blob/master/allauth/socialaccount/adapter.py
|
||||
return super().is_open_for_signup(request, sociallogin)
|
|
@ -1,10 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
from django.contrib.admin.apps import AdminConfig
|
||||
|
||||
|
||||
class SharedConfig(AppConfig):
|
||||
name = 'shared'
|
||||
|
||||
|
||||
class CustomAdminConfig(AdminConfig):
|
||||
default_site = 'shared.admin.CustomAdminSite'
|
||||
|
|
|
@ -11,7 +11,6 @@ class EventSpecificMixin(models.Model):
|
|||
verbose_name=_("évènement"),
|
||||
help_text=_("Si spécifié, l'instance du modèle"
|
||||
"est spécifique à l'évènement en question"),
|
||||
on_delete=models.CASCADE,
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
require 'compass/import-once/activate'
|
||||
# Require any additional compass plugins here.
|
||||
|
||||
# Set this to the root of your project when deployed:
|
||||
http_path = "/"
|
||||
css_dir = "css"
|
||||
sass_dir = "sass"
|
||||
images_dir = "images"
|
||||
javascripts_dir = "javascripts"
|
||||
|
||||
# You can select your preferred output style here (can be overridden via the command line):
|
||||
# output_style = :expanded or :nested or :compact or :compressed
|
||||
|
||||
# To enable relative paths to assets via compass helper functions. Uncomment:
|
||||
# relative_assets = true
|
||||
|
||||
# To disable debugging comments that display the original location of your selectors. Uncomment:
|
||||
# line_comments = false
|
||||
|
||||
|
||||
# If you prefer the indented syntax, you might want to regenerate this
|
||||
# project again passing --syntax sass, or you can uncomment this:
|
||||
# preferred_syntax = :sass
|
||||
# and then run:
|
||||
# sass-convert -R --from scss --to sass sass scss && rm -rf sass && mv scss sass
|
|
@ -1,114 +0,0 @@
|
|||
//@import "compass/css3";
|
||||
|
||||
//Variables here:
|
||||
//(alongside with commented suggestions)
|
||||
$foreground-color:#b85b3f;//black;
|
||||
$background-color:#e8e3c7;//white
|
||||
$shadow-color:#ba9186;//$foreground-color;
|
||||
$distance:8px;
|
||||
$cut-distance:3px;//$distance/4;
|
||||
$strips-size:6px; //10px
|
||||
$strips-ratio:50%;//70%
|
||||
$strips-angle:45deg;//90deg;
|
||||
|
||||
//cray stuff yo. be sure to try (if you please)
|
||||
$animate:false;//true
|
||||
$fixed:false;//true
|
||||
|
||||
body{
|
||||
font-family: 'Open Sans Condensed', sans-serif;
|
||||
font-size:85pt;
|
||||
background-color:$background-color;
|
||||
text-align:center;
|
||||
line-height:1.2em;
|
||||
padding-top:70px;
|
||||
}
|
||||
.dashed-shadow{
|
||||
position:relative;
|
||||
top:$distance;
|
||||
left:$distance;
|
||||
display:inline-block;
|
||||
color:$shadow-color;
|
||||
}
|
||||
.dashed-shadow:before{
|
||||
content:" ";
|
||||
display:block;
|
||||
|
||||
position:absolute;
|
||||
$bleeding-horizontal:10px;
|
||||
$bleeding-vertical:0px;
|
||||
top:-$bleeding-vertical - $distance;
|
||||
left:-$bleeding-vertical - $distance;
|
||||
bottom:-$bleeding-horizontal + $distance;
|
||||
right:-$bleeding-horizontal + $distance;
|
||||
z-index:1;
|
||||
$color:$background-color;
|
||||
$size:$strips-ratio/2;
|
||||
$halfSize:$size/2;
|
||||
$p1:$halfSize;
|
||||
$p2:50%-$halfSize;
|
||||
$p3:50%+$halfSize;
|
||||
$p4:100%-$halfSize;
|
||||
$transparent:transparentize($color,1);
|
||||
@include background-image(linear-gradient($strips-angle,$color $p1, $transparent $p1, $transparent $p2,$color $p2, $color $p3, $transparent $p3, $transparent $p4, $color $p4));
|
||||
background-size:$strips-size $strips-size;
|
||||
@if($animate){
|
||||
animation:dash-animation 30s infinite linear;
|
||||
}
|
||||
@if($fixed){
|
||||
background-attachment:fixed;
|
||||
}
|
||||
}
|
||||
.dashed-shadow:hover:before{
|
||||
animation:dash-animation 30s infinite linear;
|
||||
}
|
||||
|
||||
.dashed-shadow:after{
|
||||
z-index:2;
|
||||
content:attr(data-text);
|
||||
position:absolute;
|
||||
left:-$distance;
|
||||
top:-$distance;
|
||||
color:$foreground-color;
|
||||
text-shadow:$cut-distance $cut-distance $background-color;
|
||||
}
|
||||
|
||||
//fancy stuff - just useless fluff, don't mind from here onwards
|
||||
|
||||
.hello{
|
||||
font-family:'Cookie',cursive;
|
||||
font-size:140pt;
|
||||
}
|
||||
.sorta-block{
|
||||
font-size:50pt;
|
||||
line-height:1.1em;
|
||||
@include transform(skew(0,-5deg));
|
||||
z-index:3;
|
||||
position:relative;
|
||||
margin-top:20px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
.sorta{
|
||||
border-top:4px solid $foreground-color;
|
||||
border-bottom:4px solid $foreground-color;
|
||||
|
||||
text-transform:uppercase;
|
||||
z-index:3;
|
||||
//position:relative;
|
||||
//display:block;
|
||||
//width:300px;
|
||||
font-style:italic;
|
||||
}
|
||||
.hipsterish{
|
||||
font-family: 'Sancreek', cursive;
|
||||
font-size:70pt;
|
||||
}
|
||||
.dashed-shadow-text{
|
||||
font-size:140pt;
|
||||
line-height:0.7em;
|
||||
//left:-10px;
|
||||
}
|
||||
.shadow{
|
||||
font-size:120pt;
|
||||
line-height:0.8em;
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
// main: global.scss
|
||||
#filter_form {
|
||||
.form-group {
|
||||
.col-md-3, .col-md-9 {
|
||||
float: none;
|
||||
}
|
||||
ul.form-control {
|
||||
padding-left: 15px;
|
||||
list-style: none;
|
||||
height: auto;
|
||||
|
||||
a.selected {
|
||||
text-decoration: underline;
|
||||
color: darken($main_soft_color, 40%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
// main: global.scss
|
||||
@mixin active {
|
||||
&:active,
|
||||
&.active {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin hover-focus {
|
||||
&:focus,
|
||||
&.focus,
|
||||
&:hover {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin disabled {
|
||||
&.disabled,
|
||||
&[disabled],
|
||||
fieldset[disabled] & {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin dropdown-open {
|
||||
.open > &.dropdown-toggle { @content }
|
||||
}
|
||||
|
||||
@mixin btn-special {
|
||||
/**
|
||||
* This mixin applies content if the button is in at least one of the
|
||||
* following states:
|
||||
*
|
||||
* - hovered,
|
||||
* - focused,
|
||||
* - actived,
|
||||
* - is responsible of an opened dropdown.
|
||||
*
|
||||
* Where possible, state is checked from class attribute and
|
||||
* :pseudo-classes.
|
||||
*
|
||||
* ## Bootstrap compatibility
|
||||
*
|
||||
* If content defines 'color', 'background-color' and 'border', it is safe
|
||||
* to use this mixin with Bootstrap buttons as it will overrides all
|
||||
* Bootstrap color defaults of the previous cases.
|
||||
* To be precise, this covers all special important-like cases of the
|
||||
* Bootstrap mixin 'button-variant' (except the 'disabled' case).
|
||||
*
|
||||
*/
|
||||
@include hover-focus { @content }
|
||||
|
||||
@include active {
|
||||
@content;
|
||||
@include hover-focus { @content }
|
||||
}
|
||||
|
||||
@include dropdown-open {
|
||||
@content;
|
||||
@include hover-focus { @content }
|
||||
}
|
||||
}
|
||||
|
||||
@mixin button-variant-2modes($color, $background-base, $background-special, $border) {
|
||||
/**
|
||||
* This mixins allows defining color-related properties of buttons.
|
||||
*
|
||||
* It sets the following properties:
|
||||
* color: $color, except for disabled-like buttons.
|
||||
* border-color: $border.
|
||||
* background-color: Depending on button state:
|
||||
* - Default, disabled:
|
||||
* $background-base
|
||||
* - Hovered, focused, actived, responsible of an opened dropdown:
|
||||
* (one is sufficent)
|
||||
* $background-special
|
||||
*
|
||||
* ## Bootstrap compatibility
|
||||
*
|
||||
* This mixin can be used to replace colors behaviors of Bootstrap buttons.
|
||||
* Indeed, this mixin aims to replace each definition done by the
|
||||
* 'button-variant' Bootstrap mixin.
|
||||
*
|
||||
*/
|
||||
color: $color;
|
||||
background-color: $background-base;
|
||||
border-color: $border;
|
||||
|
||||
@include btn-special {
|
||||
color: $color;
|
||||
background-color: $background-special;
|
||||
border-color: $border;
|
||||
}
|
||||
|
||||
@include disabled {
|
||||
@include hover-focus {
|
||||
background-color: $background-base;
|
||||
border-color: $border;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
color: $background-base;
|
||||
background-color: $color;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@include button-variant-2modes($btn-font-color, $btn-bg-base, $btn-bg-special, $btn-border);
|
||||
}
|
||||
|
||||
form#filter_form {
|
||||
.form-group {
|
||||
padding-right:20px;
|
||||
}
|
||||
|
||||
ul.form-control {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
// main: global.scss
|
||||
.strong-banner {
|
||||
padding-top : 20px;
|
||||
padding-bottom : 10px ;
|
||||
background-color : $header-background;
|
||||
color: $header-color;
|
||||
}
|
||||
|
||||
|
||||
.navbar-inverse {
|
||||
background-color : $header-background;
|
||||
background-color : transparent ;
|
||||
border-style : none ;
|
||||
.navbar-nav {
|
||||
& > .open > a,
|
||||
& > .open > a:focus,
|
||||
& > .open > a:hover {
|
||||
color: #fff;
|
||||
background-color: $header-second-backgroud;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-collapse {
|
||||
border-top: 0px solid transparent ;
|
||||
padding : 0px ;
|
||||
|
||||
/* only < 768px*/
|
||||
background-color : $header-second-backgroud;
|
||||
padding-left: 25px;
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
background-color : transparent;
|
||||
padding-left: 0px;
|
||||
margin-left: 0px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.navbar-nav {
|
||||
width: 100%;
|
||||
@media (min-width: 768px) {
|
||||
float : right ;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-inverse {
|
||||
/* BRAND */
|
||||
.navbar-brand {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: white;
|
||||
font-family: $font_brand;
|
||||
font-size: xx-large;
|
||||
border-bottom: 5px solid $underline-brand;
|
||||
}
|
||||
}
|
||||
/* ICONE */
|
||||
.navbar-toggle {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: $second_bold_color;
|
||||
border-color: $second_bold_color;
|
||||
}
|
||||
.icon-bar {
|
||||
background-color: $second_white_color;
|
||||
}
|
||||
}
|
||||
/* LINKS */
|
||||
.navbar-nav {
|
||||
& > li > a {
|
||||
&,
|
||||
&:hover,
|
||||
&:focus {
|
||||
font-family: $font_nav;
|
||||
font-size: large;
|
||||
color: $second_bold_color;
|
||||
background: transparent;
|
||||
@media (min-width: 768px) {
|
||||
color: $second_white_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
// main: global.scss
|
||||
.message-info {
|
||||
color : #31708f;
|
||||
background-color: #d9edf7;
|
||||
border-color : #bce8f1;
|
||||
}
|
||||
.message-success {
|
||||
color : #3c763d;
|
||||
background-color: #dff0d8;
|
||||
border-color : #d6e9c6;
|
||||
}
|
||||
.message-warning {
|
||||
color : #8a6d3b;
|
||||
background-color: #fcf8e3;
|
||||
border-color : #faebcc;
|
||||
}
|
||||
.message-error {
|
||||
color : #a94442;
|
||||
background-color: #f2dede;
|
||||
border-color : #ebccd1;
|
||||
}
|
||||
.alert {
|
||||
margin-bottom: 0;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
// main: global.scss
|
||||
.tree {
|
||||
font-size:large;
|
||||
ul, li {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 32px;
|
||||
}
|
||||
|
||||
li::before,
|
||||
li::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: -12px;
|
||||
}
|
||||
li::before {
|
||||
border-top: 3px solid $second_soft_color;
|
||||
top: 9px;
|
||||
width: 8px;
|
||||
height: 0;
|
||||
}
|
||||
li::after {
|
||||
border-left: 3px solid $second_soft_color;
|
||||
height: 100%;
|
||||
width: 0px;
|
||||
top: 2px;
|
||||
}
|
||||
ul > li:last-child::after {
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.category_node {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,99 +0,0 @@
|
|||
// main: global.scss
|
||||
$main_c7: #375362;
|
||||
$main_c6: #4F778C;
|
||||
$main_c5: #5D8CA6;
|
||||
$main_c3: #BDD2DE;
|
||||
$main_c1: #F0FAFF;
|
||||
|
||||
$neutral_c2 : #F2EDDC;
|
||||
$neutral_c1: #FFFBEF;
|
||||
|
||||
$activity_c8: #4FADB8;
|
||||
$activity_c7: #5ED1DC;
|
||||
$activity_c3: #CAE4E7;
|
||||
|
||||
$event_c8: #3488A6;
|
||||
$event_c7: #3999BA;
|
||||
$event_c3: #AAD5E2;
|
||||
|
||||
$todo_c8: #F19F5D;
|
||||
$todo_c7: #FF9C4D;
|
||||
$todo_c3: #FFDEBC;
|
||||
|
||||
$equipment_c8: #E75571;
|
||||
$equipment_c7: #FF5C79;
|
||||
$equipment_c3: #FECAD6;
|
||||
|
||||
$staff_c8: #3BAD89;
|
||||
$staff_c7: #42C2A2;
|
||||
$staff_c3: #A9E1D7;
|
||||
|
||||
/* LEGACY COLORS*/
|
||||
$main_bold_color: #FF6969;
|
||||
$main_soft_color: #FF9191;
|
||||
$main_white_color: #FFEBEB;
|
||||
|
||||
$second_bold_color: #FFB363;
|
||||
$second_soft_color: #FFC282;
|
||||
$second_white_color: #FFF5EB;
|
||||
|
||||
$third_bold_color: #48B0C7;
|
||||
$third_soft_color: #8FD4E3;
|
||||
$third_white_color: #DCEAED;
|
||||
|
||||
/* COLORS */
|
||||
/* Header */
|
||||
$header-background: $main_c7;
|
||||
$header-second-backgroud: $main_c5;
|
||||
$header-color: white;
|
||||
$underline-brand: $equipment_c7;
|
||||
|
||||
/* Général */
|
||||
$html-background: $neutral_c1;
|
||||
$content-background: $neutral_c1;
|
||||
$content-border: $main_c7;
|
||||
$aside-background: $main_c7;
|
||||
$aside-inverted-background: $main_c5;
|
||||
|
||||
$title_border: $main_c7;
|
||||
|
||||
/* Le reste */
|
||||
$btn-font-color: white;
|
||||
$btn-bg-base: $main_bold_color;
|
||||
$btn-bg-special: $main_soft_color;
|
||||
$btn-border: $main_bold_color;
|
||||
|
||||
$yes_color: #55C487;
|
||||
$no_color: #E36268;
|
||||
$dunno_color: #5599C4;
|
||||
|
||||
/* Titres */
|
||||
$h1_background: $main_c6;
|
||||
$h2_background: $main_c3;
|
||||
$h3_background: $main_c1;
|
||||
|
||||
$h1_background_activity: $activity_c8;
|
||||
$h2_background_activity: $activity_c7;
|
||||
$h3_background_activity: $main_c1;
|
||||
|
||||
$h1_background_event: $event_c8;
|
||||
$h2_background_event: $event_c7;
|
||||
$h3_background_event: $main_c1;
|
||||
|
||||
$h1_background_todo: $todo_c8;
|
||||
$h2_background_todo: $todo_c7;
|
||||
$h3_background_todo: $main_c1;
|
||||
|
||||
$h1_background_equipment: $equipment_c8;
|
||||
$h2_background_equipment: $equipment_c7;
|
||||
$h3_background_equipment: $main_c1;
|
||||
|
||||
$h1_background_staff: $staff_c8;
|
||||
$h2_background_staff: $staff_c7;
|
||||
$h3_background_staff: $main_c1;
|
||||
|
||||
/* FONTS */
|
||||
$font_brand:'Lily Script One', cursive;
|
||||
$font_nav:'Work Sans', cursive;
|
||||
$font_bold:'Capriola', sans-serif;
|
||||
$font_normal:'Saira Semi Condensed', sans-serif;
|
File diff suppressed because one or more lines are too long
|
@ -1 +0,0 @@
|
|||
global.min.css
|
File diff suppressed because one or more lines are too long
1
shared/static/css/global.min.css
vendored
1
shared/static/css/global.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -1,416 +0,0 @@
|
|||
/*NE PAS MODIFIER LE FICHIER .CSS, MAIS PLUTÔT LE
|
||||
FICHIER .SCSS */
|
||||
@import '_variables';
|
||||
@import '_messages';
|
||||
@import '_header';
|
||||
@import '_forms';
|
||||
@import '_tree';
|
||||
@import '_filters';
|
||||
//@import '_dashed-shadows';
|
||||
|
||||
/* MISE EN FORME GÉNÉRALE */
|
||||
html {
|
||||
height : 100% ;
|
||||
background-color: $html-background;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font_normal;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
#principal {
|
||||
background-color: $html-background;
|
||||
}
|
||||
|
||||
/*MAIN*/
|
||||
main {
|
||||
background-color:$content-background;
|
||||
border-width: 2px;
|
||||
border-color: $content-border;
|
||||
border-style: none;
|
||||
border-collapse: collapse;
|
||||
display: table-cell;
|
||||
margin-top:0px;
|
||||
padding: 0px;
|
||||
|
||||
.fuid, h1, h2, h3 {
|
||||
margin-left: -15px;
|
||||
margin-right: -15px;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
margin-top: -2px;
|
||||
margin-bottom: -2px;
|
||||
margin-left: -17px;
|
||||
margin-right: -17px;
|
||||
color: white;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
h1 {
|
||||
font-family: $font_bold;
|
||||
font-weight: 600;
|
||||
padding-bottom: 10px;
|
||||
padding-top: 15px;
|
||||
|
||||
background-color: $h1_background;
|
||||
&.activity {
|
||||
background-color: $h1_background_activity;
|
||||
}
|
||||
&.event {
|
||||
background-color: $h1_background_event;
|
||||
}
|
||||
&.todo {
|
||||
background-color: $h1_background_todo;
|
||||
}
|
||||
&.equipment {
|
||||
background-color: $h1_background_equipment;
|
||||
}
|
||||
&.staff {
|
||||
background-color: $h1_background_staff;
|
||||
}
|
||||
}
|
||||
h2, h3 {
|
||||
border-bottom : 2px solid $main_c5;
|
||||
padding-bottom : 5px ;
|
||||
padding-top: 10px;
|
||||
font-family: $font_bold;
|
||||
font-weight: 600;
|
||||
font-size: x-large;
|
||||
|
||||
background-color: $h2_background;
|
||||
&.activity {
|
||||
background-color: $h2_background_activity;
|
||||
border-color: $activity_c8;
|
||||
}
|
||||
&.event {
|
||||
background-color: $h2_background_event;
|
||||
border-color: $event_c8;
|
||||
}
|
||||
&.todo {
|
||||
background-color: $h2_background_todo;
|
||||
border-color: $todo_c8;
|
||||
}
|
||||
&.equipment {
|
||||
background-color: $h2_background_equipment;
|
||||
border-color: $equipment_c8;
|
||||
}
|
||||
&.staff {
|
||||
background-color: $h2_background_staff;
|
||||
border-color: $staff_c8;
|
||||
}
|
||||
}
|
||||
h3 {
|
||||
background-color: $h3_background;
|
||||
color: $main_c6;
|
||||
&.activity {
|
||||
background-color: $h3_background_activity;
|
||||
color: $activity_c7;
|
||||
}
|
||||
&.event {
|
||||
background-color: $h3_background_event;
|
||||
color: $event_c7;
|
||||
}
|
||||
&.todo {
|
||||
background-color: $h3_background_todo;
|
||||
color: $todo_c7;
|
||||
}
|
||||
&.equipment {
|
||||
background-color: $h3_background_equipment;
|
||||
color: $equipment_c7;
|
||||
}
|
||||
&.staff {
|
||||
background-color: $h3_background_staff;
|
||||
color: $staff_c7;
|
||||
}
|
||||
}
|
||||
a {
|
||||
color: $main_bold_color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color : $main_bold_color;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/*ASIDE*/
|
||||
aside {
|
||||
background-color:$aside-background;
|
||||
color: white;
|
||||
margin-top: 0px;
|
||||
padding: 0px!important;
|
||||
|
||||
a {
|
||||
color: $main_soft_color;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color : $main_soft_color;
|
||||
}
|
||||
}
|
||||
|
||||
code {
|
||||
color: $main_c7;
|
||||
background-color: $main_c3;
|
||||
}
|
||||
|
||||
.heading {
|
||||
padding: 8px 15px;
|
||||
font-size: 32px;
|
||||
line-height: 1.3;
|
||||
text-align:center;
|
||||
|
||||
&.inverted {
|
||||
background-color:$second_white_color;
|
||||
color:black;
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: 25px;
|
||||
|
||||
.sub {
|
||||
font-size: 0.7em;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.sub {
|
||||
font-size: 0.7em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
&.separator {
|
||||
border-bottom-color: $main_soft_color;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
padding: 15px;
|
||||
|
||||
&.inverted {
|
||||
background-color:$second_white_color;
|
||||
color:black;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
main {
|
||||
margin-top:20px;
|
||||
border-style: dashed;
|
||||
}
|
||||
aside {
|
||||
margin-top:20px;
|
||||
}
|
||||
}
|
||||
|
||||
hr {
|
||||
border-top : 1px solid $second_bold_color ;
|
||||
}
|
||||
|
||||
|
||||
span.vsep {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
div.tag-list {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.module-list {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
a.module {
|
||||
padding: 20px 40px;
|
||||
margin: 5px;
|
||||
border-bottom-style: solid;
|
||||
font-size: large;
|
||||
display: block;
|
||||
|
||||
border-bottom-color: $main_c7;
|
||||
background-color: $main_c3;
|
||||
color: $main_c7;
|
||||
|
||||
|
||||
&.activity {
|
||||
border-bottom-color: $activity_c8;
|
||||
background-color: $activity_c3;
|
||||
color: $activity_c8;
|
||||
}
|
||||
|
||||
&.event {
|
||||
border-bottom-color: $event_c8;
|
||||
background-color: $event_c3;
|
||||
color: $event_c8;
|
||||
}
|
||||
|
||||
&.todo {
|
||||
border-bottom-color: $todo_c8;
|
||||
background-color: $todo_c3;
|
||||
color: $todo_c8;
|
||||
}
|
||||
|
||||
&.equipment {
|
||||
border-bottom-color: $equipment_c8;
|
||||
background-color: $equipment_c3;
|
||||
color: $equipment_c8;
|
||||
}
|
||||
|
||||
&.staff {
|
||||
border-bottom-color: $staff_c8;
|
||||
background-color: $staff_c3;
|
||||
color: $staff_c8;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: none;
|
||||
color: $main_c1;
|
||||
background-color: $main_c7;
|
||||
|
||||
&.activity {
|
||||
background-color: $activity_c7;
|
||||
}
|
||||
&.event {
|
||||
background-color: $event_c7;
|
||||
}
|
||||
&.equipment {
|
||||
background-color: $equipment_c7;
|
||||
}
|
||||
&.todo {
|
||||
background-color: $todo_c7;
|
||||
}
|
||||
&.staff {
|
||||
background-color: $staff_c7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collapsible {
|
||||
background-color: #777;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 18px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
text-align: left;
|
||||
outline: none;
|
||||
font-size: 15px;
|
||||
|
||||
&:after {
|
||||
content: '\002B';
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #555;
|
||||
|
||||
&:after {
|
||||
content: "\2212";
|
||||
}
|
||||
}
|
||||
.content {
|
||||
padding: 8px 18px;
|
||||
display : none;
|
||||
background-color: #f1f1f1;
|
||||
|
||||
&.fluid {
|
||||
padding: 0px 0px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: x-large;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
margin: 0px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.planning {
|
||||
.activity {
|
||||
padding: 8px 18px;
|
||||
padding-top: 8px;
|
||||
padding-bottom: 12px;
|
||||
|
||||
border-left: none;
|
||||
border-right: 6px solid $main_soft_color;
|
||||
|
||||
&.inverted {
|
||||
border-left: 6px solid $main_soft_color;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.activity-title {
|
||||
font-size: large;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.glyphicon {
|
||||
&.yes {
|
||||
color:$yes_color!important;
|
||||
}
|
||||
&.no {
|
||||
color:$no_color!important;
|
||||
}
|
||||
&.dunno {
|
||||
color:$dunno_color!important;
|
||||
}
|
||||
}
|
||||
|
||||
.sending-request {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: "Chargement...";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
opacity: 0.8;
|
||||
color: #777;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
z-index: 5;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 8%;
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
Error: Undefined variable: "$main_bold_color".
|
||||
on line 6 of header.scss
|
||||
|
||||
1: /* BANNER *\/
|
||||
2:
|
||||
3: .strong-banner {
|
||||
4: padding-top : 20px;
|
||||
5: padding-bottom : 10px ;
|
||||
6: background-color : $main_bold_color;
|
||||
7: color: $second_bold_color;
|
||||
8: }
|
||||
9:
|
||||
10:
|
||||
11: .navbar-inverse {
|
||||
|
||||
Backtrace:
|
||||
header.scss:6
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/script/tree/variable.rb:49:in `_perform'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/script/tree/node.rb:50:in `perform'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:402:in `visit_prop'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `block in visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `block in with_base'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:135:in `with_frame'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `with_base'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `block (2 levels) in visit_rule'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `map'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:444:in `block in visit_rule'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:183:in `with_environment'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:442:in `visit_rule'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `block in visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `block in with_base'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:135:in `with_frame'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/stack.rb:79:in `with_base'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:162:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `block in visit_children'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `map'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:52:in `visit_children'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:171:in `block in visit_children'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:183:in `with_environment'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:170:in `visit_children'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `block in visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:190:in `visit_root'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/base.rb:36:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:161:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/visitors/perform.rb:10:in `visit'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/root_node.rb:36:in `css_tree'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/tree/root_node.rb:29:in `render_with_sourcemap'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/engine.rb:389:in `_render_with_sourcemap'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/engine.rb:307:in `render_with_sourcemap'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:462:in `update_stylesheet'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:215:in `block in update_stylesheets'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:209:in `each'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:209:in `update_stylesheets'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin/compiler.rb:294:in `watch'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/plugin.rb:109:in `method_missing'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/sass_scss.rb:360:in `watch_or_update'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/sass_scss.rb:51:in `process_result'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/base.rb:52:in `parse'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/lib/sass/exec/base.rb:19:in `parse!'
|
||||
/usr/lib/ruby/gems/2.5.0/gems/sass-3.5.5/bin/sass:13:in `<top (required)>'
|
||||
/usr/bin/sass:23:in `load'
|
||||
/usr/bin/sass:23:in `<main>'
|
||||
*/
|
||||
body:before {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
content: "Error: Undefined variable: \"$main_bold_color\".\A on line 6 of header.scss\A \A 1: /* BANNER */\A 2: \A 3: .strong-banner {\A 4: padding-top : 20px;\A 5: padding-bottom : 10px ;\A 6: background-color : $main_bold_color;\A 7: color: $second_bold_color;\A 8: }\A 9: \A 10: \A 11: .navbar-inverse {"; }
|
126
shared/static/css/style-responsive.css
Normal file
126
shared/static/css/style-responsive.css
Normal file
|
@ -0,0 +1,126 @@
|
|||
@media (max-width: 768px) {
|
||||
|
||||
.header {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/*sidebar*/
|
||||
|
||||
#sidebar {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
|
||||
/* body container */
|
||||
#main-content {
|
||||
margin: 0px!important;
|
||||
position: none !important;
|
||||
}
|
||||
|
||||
#sidebar > ul > li > a > span {
|
||||
line-height: 35px;
|
||||
}
|
||||
|
||||
#sidebar > ul > li {
|
||||
margin: 0 10px 5px 10px;
|
||||
}
|
||||
#sidebar > ul > li > a {
|
||||
height:35px;
|
||||
line-height:35px;
|
||||
padding: 0 10px;
|
||||
text-align: left;
|
||||
}
|
||||
#sidebar > ul > li > a i{
|
||||
/*display: none !important;*/
|
||||
}
|
||||
|
||||
#sidebar ul > li > a .arrow, #sidebar > ul > li > a .arrow.open {
|
||||
margin-right: 10px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#sidebar ul > li.active > a .arrow, #sidebar ul > li > a:hover .arrow, #sidebar ul > li > a:focus .arrow,
|
||||
#sidebar > ul > li.active > a .arrow.open, #sidebar > ul > li > a:hover .arrow.open, #sidebar > ul > li > a:focus .arrow.open{
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
#sidebar > ul > li > a, #sidebar > ul > li > ul.sub > li {
|
||||
width: 100%;
|
||||
}
|
||||
#sidebar > ul > li > ul.sub > li > a {
|
||||
background: transparent !important ;
|
||||
}
|
||||
#sidebar > ul > li > ul.sub > li > a:hover {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/* sidebar */
|
||||
#sidebar {
|
||||
margin: 0px !important;
|
||||
}
|
||||
|
||||
/* sidebar collabler */
|
||||
#sidebar .btn-navbar.collapsed .arrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#sidebar .btn-navbar .arrow {
|
||||
position: absolute;
|
||||
right: 35px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
top:48px;
|
||||
border-bottom: 15px solid #282e36;
|
||||
border-left: 15px solid transparent;
|
||||
border-right: 15px solid transparent;
|
||||
}
|
||||
|
||||
|
||||
/*---------*/
|
||||
|
||||
.btn {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
ul.sidebar-menu li ul.sub li a {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/*---*/
|
||||
|
||||
.img-responsive {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@media (max-width: 480px) {
|
||||
|
||||
#top_menu .nav > li, ul.top-menu > li {
|
||||
float: right;
|
||||
}
|
||||
.hidden-phone {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width:320px) {
|
||||
#top_menu .nav > li, ul.top-menu > li {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.hidden-phone {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue