equipment main views
This commit is contained in:
parent
df0d5b4351
commit
fd4567b531
27 changed files with 747 additions and 42 deletions
|
@ -3,6 +3,7 @@ from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from django.contrib.auth.models import Group
|
from django.contrib.auth.models import Group
|
||||||
from event.models import Activity, EventSpecificMixin
|
from event.models import Activity, EventSpecificMixin
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
from .fields import IdField
|
from .fields import IdField
|
||||||
|
|
||||||
|
@ -20,20 +21,30 @@ class EquipmentCategory(models.Model):
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
default=None,
|
default=None,
|
||||||
|
related_name="children",
|
||||||
help_text=_("merci de ne pas faire de référence cyclique"),
|
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):
|
def full_name(self):
|
||||||
if self.parent is None:
|
|
||||||
return "-"
|
|
||||||
current = self
|
current = self
|
||||||
res = ""
|
res = ""
|
||||||
for k in range(100):
|
for k in range(100):
|
||||||
if current.parent is None:
|
|
||||||
break
|
|
||||||
res = "/{current}{old}".format(
|
res = "/{current}{old}".format(
|
||||||
current=current.name,
|
current=current.name,
|
||||||
old=res)
|
old=res)
|
||||||
|
if current.parent is None:
|
||||||
|
break
|
||||||
current = current.parent
|
current = current.parent
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
@ -62,6 +73,14 @@ class EquipmentCategory(models.Model):
|
||||||
return super().save(*args, **kwargs)
|
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):
|
class Equipment(EventSpecificMixin, models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
|
@ -80,11 +99,13 @@ class Equipment(EventSpecificMixin, models.Model):
|
||||||
)
|
)
|
||||||
owner = models.ForeignKey(
|
owner = models.ForeignKey(
|
||||||
Group,
|
Group,
|
||||||
|
verbose_name=_("propriétaire"),
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
EquipmentCategory,
|
EquipmentCategory,
|
||||||
|
verbose_name=_("catégorie"),
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -97,7 +118,20 @@ class Equipment(EventSpecificMixin, models.Model):
|
||||||
auto_now=True,
|
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):
|
def ids_aviable(self):
|
||||||
|
if self.stock is None:
|
||||||
|
return []
|
||||||
res = list(map(lambda x: x+1, range(self.stock)))
|
res = list(map(lambda x: x+1, range(self.stock)))
|
||||||
for lost in self.losts.all():
|
for lost in self.losts.all():
|
||||||
res = [ x
|
res = [ x
|
||||||
|
@ -116,7 +150,8 @@ class Equipment(EventSpecificMixin, models.Model):
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def stock_aviable(self):
|
def stock_aviable(self):
|
||||||
return len(self.ids_available())
|
aviable = self.ids_aviable()
|
||||||
|
return len(aviable)
|
||||||
|
|
||||||
def stock_lost(self):
|
def stock_lost(self):
|
||||||
return len(self.ids_lost())
|
return len(self.ids_lost())
|
||||||
|
@ -165,10 +200,12 @@ class EquipmentAttributeValue(models.Model):
|
||||||
Equipment,
|
Equipment,
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="attributes",
|
related_name="attributes",
|
||||||
|
verbose_name=_("matériel"),
|
||||||
help_text=_("Matériel concerné par le defaut"),
|
help_text=_("Matériel concerné par le defaut"),
|
||||||
)
|
)
|
||||||
attribute = models.ForeignKey(
|
attribute = models.ForeignKey(
|
||||||
EquipmentAttribute,
|
EquipmentAttribute,
|
||||||
|
verbose_name=_("attribut"),
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
)
|
)
|
||||||
value = models.CharField(
|
value = models.CharField(
|
||||||
|
@ -186,7 +223,7 @@ class EquipmentAttributeValue(models.Model):
|
||||||
|
|
||||||
|
|
||||||
class EquipmentAttribution(models.Model):
|
class EquipmentAttribution(models.Model):
|
||||||
equipment = models.ForeignKey(Equipment)
|
equipment = models.ForeignKey(Equipment, verbose_name=_("matériel"))
|
||||||
activity = models.ForeignKey(Activity)
|
activity = models.ForeignKey(Activity)
|
||||||
amount = models.BigIntegerField(_("quantité attribuée"))
|
amount = models.BigIntegerField(_("quantité attribuée"))
|
||||||
remarks = models.TextField(_("remarques concernant l'attribution"))
|
remarks = models.TextField(_("remarques concernant l'attribution"))
|
||||||
|
@ -211,6 +248,7 @@ class EquipmentDefault(models.Model):
|
||||||
remark = models.TextField(_("remarque sur le défaut"))
|
remark = models.TextField(_("remarque sur le défaut"))
|
||||||
equipment = models.ForeignKey(
|
equipment = models.ForeignKey(
|
||||||
Equipment,
|
Equipment,
|
||||||
|
verbose_name=_("matériel"),
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="remarks",
|
related_name="remarks",
|
||||||
help_text=_("Matériel concerné par le defaut"),
|
help_text=_("Matériel concerné par le defaut"),
|
||||||
|
@ -235,6 +273,7 @@ class EquipmentLost(models.Model):
|
||||||
)
|
)
|
||||||
equipment = models.ForeignKey(
|
equipment = models.ForeignKey(
|
||||||
Equipment,
|
Equipment,
|
||||||
|
verbose_name=_("matériel"),
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="losts",
|
related_name="losts",
|
||||||
help_text=_("Matériel concerné par la perte"),
|
help_text=_("Matériel concerné par la perte"),
|
||||||
|
@ -249,6 +288,7 @@ class EquipmentRevision(models.Model):
|
||||||
)
|
)
|
||||||
equipment = models.ForeignKey(
|
equipment = models.ForeignKey(
|
||||||
Equipment,
|
Equipment,
|
||||||
|
verbose_name=_("matériel"),
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name="revisions",
|
related_name="revisions",
|
||||||
help_text=_("Matériel concerné par les révisions"),
|
help_text=_("Matériel concerné par les révisions"),
|
||||||
|
|
108
equipment/tables.py
Normal file
108
equipment/tables.py
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
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]
|
||||||
|
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', ]
|
13
equipment/templates/equipment/detail.html
Normal file
13
equipment/templates/equipment/detail.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% 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 %}
|
44
equipment/templates/equipment/home.html
Normal file
44
equipment/templates/equipment/home.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{% extends "shared/base.html" %}
|
||||||
|
{% load i18n staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Matériel" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{% trans "Inventaire" %}</h1>
|
||||||
|
<a href="{% url 'equipment:list' %}" class="module">
|
||||||
|
<span class="glyphicon glyphicon-list-alt"></span>
|
||||||
|
{% trans "Tout le matériel" %}
|
||||||
|
</a>
|
||||||
|
<h2>{% 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">
|
||||||
|
<span class="glyphicon glyphicon-user"></span>
|
||||||
|
{{ owner.name }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<h2>{% 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 %}
|
30
equipment/templates/equipment/list.html
Normal file
30
equipment/templates/equipment/list.html
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{% extends "shared/base.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load bootstrap3 %}
|
||||||
|
{% load i18n staticfiles %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Matériel" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Inventaire</h1>
|
||||||
|
{% if subtitle %}
|
||||||
|
<h2>{{ 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 %}
|
|
@ -0,0 +1,16 @@
|
||||||
|
{% 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 %}
|
||||||
|
|
103
equipment/templates/equipment/tables/bootstrap.html
Normal file
103
equipment/templates/equipment/tables/bootstrap.html
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
{% 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 %}
|
||||||
|
|
11
equipment/templates/equipment/tree_cat.html
Normal file
11
equipment/templates/equipment/tree_cat.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<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>
|
11
equipment/urls.py
Normal file
11
equipment/urls.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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'),
|
||||||
|
]
|
97
equipment/views.py
Normal file
97
equipment/views.py
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
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(DetailView):
|
||||||
|
model = Equipment
|
||||||
|
template_name = 'equipment/detail.html'
|
|
@ -60,6 +60,9 @@ INSTALLED_APPS = [
|
||||||
'bootstrapform',
|
'bootstrapform',
|
||||||
'widget_tweaks',
|
'widget_tweaks',
|
||||||
'taggit',
|
'taggit',
|
||||||
|
'django_tables2',
|
||||||
|
'django_filters',
|
||||||
|
'bootstrap3',
|
||||||
|
|
||||||
'allauth_ens',
|
'allauth_ens',
|
||||||
'allauth',
|
'allauth',
|
||||||
|
@ -189,8 +192,8 @@ CAS_EMAIL_FORMAT = "%s@clipper.ens.fr"
|
||||||
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
|
CAS_FORCE_CHANGE_USERNAME_CASE = "lower"
|
||||||
CAS_VERSION = 'CAS_2_SAML_1_0'
|
CAS_VERSION = 'CAS_2_SAML_1_0'
|
||||||
|
|
||||||
LOGIN_URL = reverse_lazy('users:login')
|
LOGIN_URL = reverse_lazy('account_login')
|
||||||
LOGOUT_URL = reverse_lazy('users:logout')
|
LOGOUT_URL = reverse_lazy('account_logout')
|
||||||
LOGIN_REDIRECT_URL = reverse_lazy('shared:home')
|
LOGIN_REDIRECT_URL = reverse_lazy('shared:home')
|
||||||
ACCOUNT_HOME_URL = reverse_lazy('shared:home')
|
ACCOUNT_HOME_URL = reverse_lazy('shared:home')
|
||||||
ACCOUNT_DETAILS_URL = reverse_lazy('shared:home')
|
ACCOUNT_DETAILS_URL = reverse_lazy('shared:home')
|
||||||
|
|
|
@ -18,6 +18,7 @@ urlpatterns = [
|
||||||
# Admin
|
# Admin
|
||||||
url(r'^admin/', admin_site.urls),
|
url(r'^admin/', admin_site.urls),
|
||||||
# Apps
|
# Apps
|
||||||
|
url(r'^equipment/', include('equipment.urls')),
|
||||||
url(r'^event/', include('event.urls')),
|
url(r'^event/', include('event.urls')),
|
||||||
url(r'^user/', include('users.urls')),
|
url(r'^user/', include('users.urls')),
|
||||||
# REST
|
# REST
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
asgi-redis==1.3.0
|
asgi-redis==1.3.0
|
||||||
asgiref==1.1.1
|
asgiref==1.1.1
|
||||||
|
django-bootstrap3==10.0.1
|
||||||
channels==1.1.5
|
channels==1.1.5
|
||||||
Django==1.11.11
|
Django==1.11.11
|
||||||
django-allauth==0.36.0
|
django-allauth==0.36.0
|
||||||
django-allauth-cas==1.0.0b2
|
django-allauth-cas==1.0.0b2
|
||||||
|
django-tables2==2.0.0a2
|
||||||
|
django-filter==2.0.0
|
||||||
#git+https://git.eleves.ens.fr/cof-geek/django-allauth-ens@6e77b31e0dfed7659776d
|
#git+https://git.eleves.ens.fr/cof-geek/django-allauth-ens@6e77b31e0dfed7659776d
|
||||||
git+https://git.eleves.ens.fr/cof-geek/django-allauth-ens@30a072b7db1fc2d4652284227a3e7e876b14c915
|
git+https://git.eleves.ens.fr/cof-geek/django-allauth-ens@30a072b7db1fc2d4652284227a3e7e876b14c915
|
||||||
django-bootstrap-form==3.2.1
|
django-bootstrap-form==3.2.1
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -108,3 +108,14 @@
|
||||||
@include button-variant-2modes($btn-font-color, $btn-bg-base, $btn-bg-special, $btn-border);
|
@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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -158,17 +158,49 @@
|
||||||
color: #FF6969;
|
color: #FF6969;
|
||||||
background-color: white; }
|
background-color: white; }
|
||||||
|
|
||||||
|
form#filter_form .form-group {
|
||||||
|
padding-right: 20px; }
|
||||||
|
form#filter_form ul.form-control {
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none; }
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
font-size: large; }
|
||||||
|
.tree ul, .tree li {
|
||||||
|
position: relative; }
|
||||||
|
.tree ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 32px; }
|
||||||
|
.tree li::before,
|
||||||
|
.tree li::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: -12px; }
|
||||||
|
.tree li::before {
|
||||||
|
border-top: 3px solid #FFC282;
|
||||||
|
top: 9px;
|
||||||
|
width: 8px;
|
||||||
|
height: 0; }
|
||||||
|
.tree li::after {
|
||||||
|
border-left: 3px solid #FFC282;
|
||||||
|
height: 100%;
|
||||||
|
width: 0px;
|
||||||
|
top: 2px; }
|
||||||
|
.tree ul > li:last-child::after {
|
||||||
|
height: 8px; }
|
||||||
|
|
||||||
/* MISE EN FORME GÉNÉRALE */
|
/* MISE EN FORME GÉNÉRALE */
|
||||||
html {
|
html {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #FFF5EB; }
|
background-color: #DCEAED; }
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Saira Semi Condensed", sans-serif;
|
font-family: "Saira Semi Condensed", sans-serif;
|
||||||
font-size: medium; }
|
font-size: medium; }
|
||||||
|
|
||||||
#principal {
|
#principal {
|
||||||
background-color: #FFF5EB; }
|
background-color: #DCEAED; }
|
||||||
|
|
||||||
/*MAIN*/
|
/*MAIN*/
|
||||||
main {
|
main {
|
||||||
|
@ -182,13 +214,13 @@ main {
|
||||||
main h1 {
|
main h1 {
|
||||||
border-bottom: 7px solid #FFB363;
|
border-bottom: 7px solid #FFB363;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
font-family: "Saira", sans-serif;
|
font-family: "Capriola", sans-serif;
|
||||||
font-weight: 600; }
|
font-weight: 600; }
|
||||||
main h2 {
|
main h2 {
|
||||||
border-bottom: 2px solid #FF6969;
|
border-bottom: 2px solid #FF6969;
|
||||||
color: #FF6969;
|
color: #FF6969;
|
||||||
padding-bottom: 5px;
|
padding-bottom: 5px;
|
||||||
font-family: "Saira", sans-serif;
|
font-family: "Capriola", sans-serif;
|
||||||
font-weight: 600; }
|
font-weight: 600; }
|
||||||
|
|
||||||
/*ASIDE*/
|
/*ASIDE*/
|
||||||
|
@ -196,7 +228,7 @@ aside {
|
||||||
background-color: #FFC282;
|
background-color: #FFC282;
|
||||||
color: white;
|
color: white;
|
||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
padding: 15px; }
|
padding: 0px !important; }
|
||||||
aside a {
|
aside a {
|
||||||
color: #FF9191; }
|
color: #FF9191; }
|
||||||
aside a:hover, aside a:active, aside a:focus {
|
aside a:hover, aside a:active, aside a:focus {
|
||||||
|
@ -204,6 +236,30 @@ aside {
|
||||||
aside code {
|
aside code {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: #FFB363; }
|
background-color: #FFB363; }
|
||||||
|
aside .heading {
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 32px;
|
||||||
|
line-height: 1.3;
|
||||||
|
text-align: center; }
|
||||||
|
aside .heading.inverted {
|
||||||
|
background-color: #FFF5EB;
|
||||||
|
color: black; }
|
||||||
|
aside .heading.small {
|
||||||
|
font-size: 25px; }
|
||||||
|
aside .heading.small .sub {
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-weight: normal; }
|
||||||
|
aside .heading .sub {
|
||||||
|
font-size: 0.7em;
|
||||||
|
font-weight: normal; }
|
||||||
|
aside .heading.separator {
|
||||||
|
border-bottom-color: #FF9191;
|
||||||
|
border-bottom-style: solid; }
|
||||||
|
aside .text {
|
||||||
|
padding: 15px; }
|
||||||
|
aside .text.inverted {
|
||||||
|
background-color: #FFF5EB;
|
||||||
|
color: black; }
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
main {
|
main {
|
||||||
|
@ -224,6 +280,25 @@ div.tag-list {
|
||||||
code {
|
code {
|
||||||
font-size: small; }
|
font-size: small; }
|
||||||
|
|
||||||
|
.module-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap; }
|
||||||
|
|
||||||
|
a.module {
|
||||||
|
background-color: #FFB363;
|
||||||
|
color: #FFF5EB;
|
||||||
|
padding: 20px 40px;
|
||||||
|
margin: 5px;
|
||||||
|
border-bottom-color: #FF9191;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
font-size: large;
|
||||||
|
display: block; }
|
||||||
|
a.module:hover, a.module:active, a.module:focus {
|
||||||
|
color: #FFF5EB;
|
||||||
|
background-color: #FFC282;
|
||||||
|
text-decoration: none; }
|
||||||
|
|
||||||
/* COULD BE USERFULL LATER
|
/* COULD BE USERFULL LATER
|
||||||
|
|
||||||
.row-eq-height {
|
.row-eq-height {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"version": 3,
|
"version": 3,
|
||||||
"mappings": "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",
|
"mappings": "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",
|
||||||
"sources": ["global.scss","variables.scss","messages.scss","header.scss","forms.scss"],
|
"sources": ["global.scss","variables.scss","messages.scss","header.scss","forms.scss","tree.scss"],
|
||||||
"names": [],
|
"names": [],
|
||||||
"file": "global.css"
|
"file": "global.css"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,12 @@
|
||||||
@import 'messages';
|
@import 'messages';
|
||||||
@import 'header';
|
@import 'header';
|
||||||
@import 'forms';
|
@import 'forms';
|
||||||
|
@import 'tree';
|
||||||
|
|
||||||
/* MISE EN FORME GÉNÉRALE */
|
/* MISE EN FORME GÉNÉRALE */
|
||||||
html {
|
html {
|
||||||
height : 100% ;
|
height : 100% ;
|
||||||
background-color: $second_white_color;
|
background-color: $third_white_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
@ -17,7 +18,7 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#principal {
|
#principal {
|
||||||
background-color: $second_white_color;
|
background-color: $third_white_color;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*MAIN*/
|
/*MAIN*/
|
||||||
|
@ -55,7 +56,7 @@ aside {
|
||||||
background-color:$second_soft_color;
|
background-color:$second_soft_color;
|
||||||
color: white;
|
color: white;
|
||||||
margin-top:0px;
|
margin-top:0px;
|
||||||
padding: 15px;
|
padding: 0px!important;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: $main_soft_color;
|
color: $main_soft_color;
|
||||||
|
@ -70,6 +71,45 @@ aside {
|
||||||
color: white;
|
color: white;
|
||||||
background-color: $second_bold_color;
|
background-color: $second_bold_color;
|
||||||
}
|
}
|
||||||
|
.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) {
|
@media (min-width: 768px) {
|
||||||
|
@ -98,6 +138,32 @@ div.tag-list {
|
||||||
code {
|
code {
|
||||||
font-size: small;
|
font-size: small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.module-list {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.module {
|
||||||
|
background-color: $second_bold_color;
|
||||||
|
color: $second_white_color;
|
||||||
|
padding: 20px 40px;
|
||||||
|
margin: 5px;
|
||||||
|
border-bottom-color: $main_soft_color;
|
||||||
|
border-bottom-style: solid;
|
||||||
|
font-size: large;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
color: $second_white_color;
|
||||||
|
background-color: $second_soft_color;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* COULD BE USERFULL LATER
|
/* COULD BE USERFULL LATER
|
||||||
|
|
||||||
.row-eq-height {
|
.row-eq-height {
|
||||||
|
|
40
shared/static/css/tree.scss
Normal file
40
shared/static/css/tree.scss
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
.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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,11 @@ $second_bold_color: #FFB363;
|
||||||
$second_soft_color: #FFC282;
|
$second_soft_color: #FFC282;
|
||||||
$second_white_color: #FFF5EB;
|
$second_white_color: #FFF5EB;
|
||||||
|
|
||||||
|
$third_bold_color: #48B0C7;
|
||||||
|
$third_soft_color: #8FD4E3;
|
||||||
|
$third_white_color: #DCEAED;
|
||||||
|
|
||||||
|
|
||||||
$btn-font-color: white;
|
$btn-font-color: white;
|
||||||
$btn-bg-base: $main_bold_color;
|
$btn-bg-base: $main_bold_color;
|
||||||
$btn-bg-special: $main_soft_color;
|
$btn-bg-special: $main_soft_color;
|
||||||
|
@ -16,5 +21,5 @@ $underline-brand: $second_soft_color;
|
||||||
/* FONTS */
|
/* FONTS */
|
||||||
$font_brand:'Lily Script One', cursive;
|
$font_brand:'Lily Script One', cursive;
|
||||||
$font_nav:'Work Sans', cursive;
|
$font_nav:'Work Sans', cursive;
|
||||||
$font_bold:'Saira', sans-serif;
|
$font_bold:'Capriola', sans-serif;
|
||||||
$font_normal:'Saira Semi Condensed', sans-serif;
|
$font_normal:'Saira Semi Condensed', sans-serif;
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
{# Fonts #}
|
{# Fonts #}
|
||||||
<link href="{% static "fonts/glyphicons-halflings-regular.ttf" %}">
|
<link href="{% static "fonts/glyphicons-halflings-regular.ttf" %}">
|
||||||
<link href="https://fonts.googleapis.com/css?family=Lily+Script+One|Saira+Semi+Condensed|Saira:600|Work+Sans" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css?family=Lily+Script+One|Saira+Semi+Condensed|Capriola|Work+Sans" rel="stylesheet">
|
||||||
|
|
||||||
{# Global stylesheets #}
|
{# Global stylesheets #}
|
||||||
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
<link href="{% static "css/bootstrap.min.css" %}" rel="stylesheet">
|
||||||
|
@ -57,10 +57,10 @@
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% block real_content %}
|
{% block real_content %}
|
||||||
<main class="col-sm-8">
|
<main class="col-lg-9">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
</main>
|
</main>
|
||||||
<aside class="col-sm-4">
|
<aside class="col-lg-3">
|
||||||
{{ message_title }}
|
{{ message_title }}
|
||||||
{% block aside %}{% endblock %}
|
{% block aside %}{% endblock %}
|
||||||
</aside>
|
</aside>
|
||||||
|
|
|
@ -4,14 +4,35 @@
|
||||||
{% block page_title %}{% trans "Accueil" %}{% endblock %}
|
{% block page_title %}{% trans "Accueil" %}{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<h1>Bienvenue sur Poulpe !</h1>
|
||||||
|
<h2>Modules</h2>
|
||||||
|
<div class="module-list">
|
||||||
|
<a href="{% url 'equipment:home' %}" class="module">
|
||||||
|
<span class="glyphicon glyphicon-list-alt"></span>
|
||||||
|
Inventaire
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'equipment:home' %}" class="module">
|
||||||
|
<span class="glyphicon glyphicon-calendar"></span>
|
||||||
|
Évènements
|
||||||
|
</a>
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<a href="{% url 'admin:index' %}" class="module">
|
||||||
|
<span class="glyphicon glyphicon-cog"></span>
|
||||||
|
Administration
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Outil en construction</h2>
|
<h2>Outil en construction</h2>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
<p>Toutes les fonctionnalités ne sont pas encore implémentées et le site est encore en version de test. Pour le moment les fonctionnalités qui fonctionnent sont :
|
<p>Toutes les fonctionnalités ne sont pas encore implémentées et le site est encore en version de test. Pour le moment les fonctionnalités qui fonctionnent sont :
|
||||||
<ul>
|
<ul>
|
||||||
<li>La gestion du matériel (uniquement via l'interface d'administration)</li>
|
<li>La gestion du matériel (uniquement via l'interface d'administration)</li>
|
||||||
<li>Connexion via CAS</li>
|
<li>Connexion via CAS</li>
|
||||||
</ul>
|
</ul>
|
||||||
</p>
|
</p>
|
||||||
<img src="{% static "img/poulpe.png"%}" class="img-fluid" alt="Responsive image" style="width: 100%; height: auto;">
|
|
||||||
<p>Dans la liste des choses qui sont partiellement ou totalement à implémenter :
|
<p>Dans la liste des choses qui sont partiellement ou totalement à implémenter :
|
||||||
<ul>
|
<ul>
|
||||||
<li>Création/gestion d'évènements</li>
|
<li>Création/gestion d'évènements</li>
|
||||||
|
@ -23,13 +44,20 @@
|
||||||
<li>Tags sur les activités</li>
|
<li>Tags sur les activités</li>
|
||||||
<li>...</li>
|
<li>...</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
</p>
|
</p>
|
||||||
|
<img src="{% static "img/poulpe.png"%}" class="img-fluid" alt="Responsive image" style="width: 100%; height: auto;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block aside %}
|
{% block aside %}
|
||||||
|
<div class="text">
|
||||||
<h2>Envie de nous donner un coup de main ?</h2>
|
<h2>Envie de nous donner un coup de main ?</h2>
|
||||||
<p>À terme le but de cet outil est de fournir une interface ergnonomique et efficace pour l'organisation d'évènements, petits ou grands, en permettant notament de proposer des taches et/ou des permances auquel les utilisat⋅rice⋅eur⋅s pourront ensuite s'inscrire.<p>
|
<p>À terme le but de cet outil est de fournir une interface ergnonomique et efficace pour l'organisation d'évènements, petits ou grands, en permettant notament de proposer des taches et/ou des permances auquel les utilisat⋅rice⋅eur⋅s pourront ensuite s'inscrire.<p>
|
||||||
<p>Le projet est essentiellement codé en <code>Django</code>, un framework <code>python</code> pour le web, mais nous avons aussi besoin de mains pour écrire du <code>JavaScript</code>, <code>HTML</code>, <code>CSS</code> ou même pour réaliser des illustrations ! Nous organisons de temps en temps des sessions aucours desquelles il est possible d'apprendre à utiliser ces outils même sans avoir de connaissances préalables. Si le projet t'intéresse et que tu veux y participer, n'hésite pas à nous envoyer un mail à <code>cof-geek (at) ens (point) fr</code> :)</p>
|
<p>Le projet est essentiellement codé en <code>Django</code>, un framework <code>python</code> pour le web, mais nous avons aussi besoin de mains pour écrire du <code>JavaScript</code>, <code>HTML</code>, <code>CSS</code> ou même pour réaliser des illustrations ! Nous organisons de temps en temps des sessions aucours desquelles il est possible d'apprendre à utiliser ces outils même sans avoir de connaissances préalables. Si le projet t'intéresse et que tu veux y participer, n'hésite pas à nous envoyer un mail à <code>cof-geek (at) ens (point) fr</code> :)</p>
|
||||||
<p>Tu peux retrouver tous nos projets sur le <a href="https://git.eleves.ens.fr/cof-geek/">serveur git de l'École</a>, ainsi qu'une présentation plus détaillée de <code>COF-Geek</code> en <a href="https://cof.ens.fr/cof-geek/">suivant ce lien</a>.</p>
|
<p>Tu peux retrouver tous nos projets sur le <a href="https://git.eleves.ens.fr/cof-geek/">serveur git de l'École</a>, ainsi qu'une présentation plus détaillée de <code>COF-Geek</code> en <a href="https://cof.ens.fr/cof-geek/">suivant ce lien</a>.</p>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue