Commit f9d4e1c2 authored by Och's avatar Och

Corrected conflicts

parents 18c0809f f34f5fe6
Pipeline #2225 failed with stage
in 29 minutes and 55 seconds
......@@ -33,8 +33,8 @@ from core.views import can_view, can_edit
def check_if(obj, user, test):
"""
Detect if it's a single object or a queryset
aply a given test on individual object and return global permission
Detect if it's a single object or a queryset
aply a given test on individual object and return global permission
"""
if isinstance(obj, QuerySet):
for o in obj:
......@@ -49,7 +49,7 @@ class ManageModelMixin:
@action(detail=True)
def id(self, request, pk=None):
"""
Get by id (api/v1/router/{pk}/id/)
Get by id (api/v1/router/{pk}/id/)
"""
self.queryset = get_object_or_404(self.queryset.filter(id=pk))
serializer = self.get_serializer(self.queryset)
......
......@@ -33,7 +33,7 @@ from core.templatetags.renderer import markdown
@renderer_classes((StaticHTMLRenderer,))
def RenderMarkdown(request):
"""
Render Markdown
Render Markdown
"""
try:
data = markdown(request.POST["text"])
......
......@@ -43,7 +43,7 @@ class ClubSerializer(serializers.ModelSerializer):
class ClubViewSet(RightModelViewSet):
"""
Manage Clubs (api/v1/club/)
Manage Clubs (api/v1/club/)
"""
serializer_class = ClubSerializer
......
......@@ -45,7 +45,7 @@ class CounterSerializer(serializers.ModelSerializer):
class CounterViewSet(RightModelViewSet):
"""
Manage Counters (api/v1/counter/)
Manage Counters (api/v1/counter/)
"""
serializer_class = CounterSerializer
......@@ -54,7 +54,7 @@ class CounterViewSet(RightModelViewSet):
@action(detail=False)
def bar(self, request):
"""
Return all bars (api/v1/counter/bar/)
Return all bars (api/v1/counter/bar/)
"""
self.queryset = self.queryset.filter(type="BAR")
serializer = self.get_serializer(self.queryset, many=True)
......
......@@ -36,7 +36,7 @@ class GroupSerializer(serializers.ModelSerializer):
class GroupViewSet(RightModelViewSet):
"""
Manage Groups (api/v1/group/)
Manage Groups (api/v1/group/)
"""
serializer_class = GroupSerializer
......
......@@ -72,7 +72,7 @@ class LaunderetteTokenSerializer(serializers.ModelSerializer):
class LaunderettePlaceViewSet(RightModelViewSet):
"""
Manage Launderette (api/v1/launderette/place/)
Manage Launderette (api/v1/launderette/place/)
"""
serializer_class = LaunderettePlaceSerializer
......@@ -81,7 +81,7 @@ class LaunderettePlaceViewSet(RightModelViewSet):
class LaunderetteMachineViewSet(RightModelViewSet):
"""
Manage Washing Machines (api/v1/launderette/machine/)
Manage Washing Machines (api/v1/launderette/machine/)
"""
serializer_class = LaunderetteMachineSerializer
......@@ -90,7 +90,7 @@ class LaunderetteMachineViewSet(RightModelViewSet):
class LaunderetteTokenViewSet(RightModelViewSet):
"""
Manage Launderette's tokens (api/v1/launderette/token/)
Manage Launderette's tokens (api/v1/launderette/token/)
"""
serializer_class = LaunderetteTokenSerializer
......@@ -99,7 +99,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
@action(detail=False)
def washing(self, request):
"""
Return all washing tokens (api/v1/launderette/token/washing)
Return all washing tokens (api/v1/launderette/token/washing)
"""
self.queryset = self.queryset.filter(type="WASHING")
serializer = self.get_serializer(self.queryset, many=True)
......@@ -108,7 +108,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
@action(detail=False)
def drying(self, request):
"""
Return all drying tokens (api/v1/launderette/token/drying)
Return all drying tokens (api/v1/launderette/token/drying)
"""
self.queryset = self.queryset.filter(type="DRYING")
serializer = self.get_serializer(self.queryset, many=True)
......@@ -117,7 +117,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
@action(detail=False)
def avaliable(self, request):
"""
Return all avaliable tokens (api/v1/launderette/token/avaliable)
Return all avaliable tokens (api/v1/launderette/token/avaliable)
"""
self.queryset = self.queryset.filter(
borrow_date__isnull=True, user__isnull=True
......@@ -128,7 +128,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
@action(detail=False)
def unavaliable(self, request):
"""
Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
"""
self.queryset = self.queryset.filter(
borrow_date__isnull=False, user__isnull=False
......
......@@ -50,8 +50,8 @@ class UserSerializer(serializers.ModelSerializer):
class UserViewSet(RightModelViewSet):
"""
Manage Users (api/v1/user/)
Only show active users
Manage Users (api/v1/user/)
Only show active users
"""
serializer_class = UserSerializer
......@@ -60,7 +60,7 @@ class UserViewSet(RightModelViewSet):
@action(detail=False)
def birthday(self, request):
"""
Return all users born today (api/v1/user/birstdays)
Return all users born today (api/v1/user/birstdays)
"""
date = datetime.datetime.today()
self.queryset = self.queryset.filter(date_of_birth=date)
......
......@@ -29,10 +29,10 @@ def uv_endpoint(request):
def find_uv(lang, year, code):
"""
Uses the UTBM API to find an UV.
short_uv is the UV entry in the UV list. It is returned as it contains
information which are not in full_uv.
full_uv is the detailed representation of an UV.
Uses the UTBM API to find an UV.
short_uv is the UV entry in the UV list. It is returned as it contains
information which are not in full_uv.
full_uv is the detailed representation of an UV.
"""
# query the UV list
uvs_url = settings.SITH_PEDAGOGY_UTBM_API + "/uvs/{}/{}".format(lang, year)
......@@ -57,7 +57,7 @@ def find_uv(lang, year, code):
def make_clean_uv(short_uv, full_uv):
"""
Cleans the data up so that it corresponds to our data representation.
Cleans the data up so that it corresponds to our data representation.
"""
res = {}
......
......@@ -157,7 +157,7 @@ class MailingForm(forms.Form):
return cleaned_data
class SellingsFormBase(forms.Form):
class SellingsForm(forms.Form):
begin_date = forms.DateTimeField(
input_formats=["%Y-%m-%d %H:%M:%S"],
label=_("Begin date"),
......@@ -170,10 +170,24 @@ class SellingsFormBase(forms.Form):
required=False,
widget=SelectDateTime,
)
counter = forms.ModelChoiceField(
counters = forms.ModelMultipleChoiceField(
Counter.objects.order_by("name").all(), label=_("Counter"), required=False
)
def __init__(self, club, *args, **kwargs):
super(SellingsForm, self).__init__(*args, **kwargs)
self.fields["products"] = forms.ModelMultipleChoiceField(
club.products.order_by("name").filter(archived=False).all(),
label=_("Products"),
required=False,
)
self.fields["archived_products"] = forms.ModelMultipleChoiceField(
club.products.order_by("name").filter(archived=True).all(),
label=_("Archived products"),
required=False,
)
class ClubMemberForm(forms.Form):
"""
......@@ -238,8 +252,8 @@ class ClubMemberForm(forms.Form):
def clean_users(self):
"""
Check that the user is not trying to add an user already in the club
Also check that the user is valid and has a valid subscription
Check that the user is not trying to add an user already in the club
Also check that the user is valid and has a valid subscription
"""
cleaned_data = super(ClubMemberForm, self).clean()
users = []
......@@ -262,7 +276,7 @@ class ClubMemberForm(forms.Form):
def clean(self):
"""
Check user rights for adding an user
Check user rights for adding an user
"""
cleaned_data = super(ClubMemberForm, self).clean()
......
{% extends "core/base.jinja" %}
{% from 'core/macros.jinja' import user_profile_link %}
{% from 'core/macros.jinja' import user_profile_link, paginate %}
{% block content %}
<h3>{% trans %}Sellings{% endtrans %}</h3>
<form action="" method="get">
<form id="form" action="?page=1" method="post">
{% csrf_token %}
{{ form }}
<p><input type="submit" value="{% trans %}Show{% endtrans %}" /></p>
......@@ -28,7 +28,7 @@
</tr>
</thead>
<tbody>
{% for s in result %}
{% for s in paginated_result %}
<tr>
<td>{{ s.date|localtime|date(DATETIME_FORMAT) }} {{ s.date|localtime|time(DATETIME_FORMAT) }}</td>
<td>{{ s.counter }}</td>
......@@ -53,6 +53,14 @@
{% endfor %}
</tbody>
</table>
<script type="text/javascript">
function formPagination(link){
$("form").attr("action", link.href);
link.href = "javascript:void(0)"; // block link action
$("form").submit();
}
</script>
{{ paginate(paginated_result, paginator, "formPagination(this)") }}
{% endblock %}
......
......@@ -23,6 +23,7 @@
#
#
import csv
from django.conf import settings
from django import forms
......@@ -30,13 +31,21 @@ from django.views.generic import ListView, DetailView, TemplateView, View
from django.views.generic.edit import DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView, CreateView
from django.http import HttpResponseRedirect, HttpResponse, Http404
from django.http import (
HttpResponseRedirect,
HttpResponse,
Http404,
StreamingHttpResponse,
)
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.utils.translation import ugettext as _t
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
from django.core.paginator import Paginator, InvalidPage
from django.shortcuts import get_object_or_404, redirect
from django.db.models import Sum
from core.views import (
CanCreateMixin,
......@@ -60,7 +69,7 @@ from com.views import (
)
from club.models import Club, Membership, Mailing, MailingSubscription
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsFormBase
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm
class ClubTabsMixin(TabedViewMixin):
......@@ -281,7 +290,7 @@ class ClubMembersView(ClubTabsMixin, CanViewMixin, DetailFormView):
def form_valid(self, form):
"""
Check user rights
Check user rights
"""
resp = super(ClubMembersView, self).form_valid(form)
......@@ -319,7 +328,7 @@ class ClubOldMembersView(ClubTabsMixin, CanViewMixin, DetailView):
current_tab = "elderlies"
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailFormView):
"""
Sellings of a club
"""
......@@ -328,21 +337,35 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
pk_url_kwarg = "club_id"
template_name = "club/club_sellings.jinja"
current_tab = "sellings"
form_class = SellingsForm
paginate_by = 70
def get_form_class(self):
kwargs = {
"product": forms.ModelChoiceField(
self.object.products.order_by("name").all(),
label=_("Product"),
required=False,
)
}
return type("SellingsForm", (SellingsFormBase,), kwargs)
def dispatch(self, request, *args, **kwargs):
try:
self.asked_page = int(request.GET.get("page", 1))
except ValueError:
raise Http404
return super(ClubSellingView, self).dispatch(request, *args, **kwargs)
def get_form_kwargs(self):
kwargs = super(ClubSellingView, self).get_form_kwargs()
kwargs["club"] = self.object
return kwargs
def post(self, request, *args, **kwargs):
return self.get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
kwargs = super(ClubSellingView, self).get_context_data(**kwargs)
form = self.get_form_class()(self.request.GET)
qs = Selling.objects.filter(club=self.object)
kwargs["result"] = qs[:0]
kwargs["paginated_result"] = kwargs["result"]
kwargs["total"] = 0
kwargs["total_quantity"] = 0
kwargs["benefit"] = 0
form = self.get_form()
if form.is_valid():
if not len([v for v in form.cleaned_data.values() if v is not None]):
qs = Selling.objects.filter(id=-1)
......@@ -350,19 +373,36 @@ class ClubSellingView(ClubTabsMixin, CanEditMixin, DetailView):
qs = qs.filter(date__gte=form.cleaned_data["begin_date"])
if form.cleaned_data["end_date"]:
qs = qs.filter(date__lte=form.cleaned_data["end_date"])
if form.cleaned_data["counter"]:
qs = qs.filter(counter=form.cleaned_data["counter"])
if form.cleaned_data["product"]:
qs = qs.filter(product__id=form.cleaned_data["product"].id)
if form.cleaned_data["counters"]:
qs = qs.filter(counter__in=form.cleaned_data["counters"])
selected_products = []
if form.cleaned_data["products"]:
selected_products.extend(form.cleaned_data["products"])
if form.cleaned_data["archived_products"]:
selected_products.extend(form.cleaned_data["archived_products"])
if len(selected_products) > 0:
qs = qs.filter(product__in=selected_products)
kwargs["result"] = qs.all().order_by("-id")
kwargs["total"] = sum([s.quantity * s.unit_price for s in qs.all()])
kwargs["total_quantity"] = sum([s.quantity for s in qs.all()])
kwargs["benefit"] = kwargs["total"] - sum(
[s.product.purchase_price for s in qs.exclude(product=None)]
kwargs["total"] = sum([s.quantity * s.unit_price for s in kwargs["result"]])
total_quantity = qs.all().aggregate(Sum("quantity"))
if total_quantity["quantity__sum"]:
kwargs["total_quantity"] = total_quantity["quantity__sum"]
benefit = (
qs.exclude(product=None).all().aggregate(Sum("product__purchase_price"))
)
else:
kwargs["result"] = qs[:0]
kwargs["form"] = form
if benefit["product__purchase_price__sum"]:
kwargs["benefit"] = benefit["product__purchase_price__sum"]
kwargs["paginator"] = Paginator(kwargs["result"], self.paginate_by)
try:
kwargs["paginated_result"] = kwargs["paginator"].page(self.asked_page)
except InvalidPage:
raise Http404
return kwargs
......@@ -371,16 +411,46 @@ class ClubSellingCSVView(ClubSellingView):
Generate sellings in csv for a given period
"""
class StreamWriter:
"""Implements a file-like interface for streaming the CSV"""
def write(self, value):
"""Write the value by returning it, instead of storing in a buffer."""
return value
def write_selling(self, selling):
row = [selling.date, selling.counter]
if selling.seller:
row.append(selling.seller.get_display_name())
else:
row.append("")
if selling.customer:
row.append(selling.customer.user.get_display_name())
else:
row.append("")
row = row + [
selling.label,
selling.quantity,
selling.quantity * selling.unit_price,
selling.get_payment_method_display(),
]
if selling.product:
row.append(selling.product.selling_price)
row.append(selling.product.purchase_price)
row.append(selling.product.selling_price - selling.product.purchase_price)
else:
row = row + ["", "", ""]
return row
def get(self, request, *args, **kwargs):
import csv
response = HttpResponse(content_type="text/csv")
self.object = self.get_object()
name = _("Sellings") + "_" + self.object.name + ".csv"
response["Content-Disposition"] = "filename=" + name
kwargs = self.get_context_data(**kwargs)
# Use the StreamWriter class instead of request for streaming
pseudo_buffer = self.StreamWriter()
writer = csv.writer(
response, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL
pseudo_buffer, delimiter=";", lineterminator="\n", quoting=csv.QUOTE_ALL
)
writer.writerow([_t("Quantity"), kwargs["total_quantity"]])
......@@ -401,29 +471,17 @@ class ClubSellingCSVView(ClubSellingView):
_t("Benefit"),
]
)
for o in kwargs["result"]:
row = [o.date, o.counter]
if o.seller:
row.append(o.seller.get_display_name())
else:
row.append("")
if o.customer:
row.append(o.customer.user.get_display_name())
else:
row.append("")
row = row + [
o.label,
o.quantity,
o.quantity * o.unit_price,
o.get_payment_method_display(),
]
if o.product:
row.append(o.product.selling_price)
row.append(o.product.purchase_price)
row.append(o.product.selling_price - o.product.purchase_price)
else:
row = row + ["", "", ""]
writer.writerow(row)
# Stream response
response = StreamingHttpResponse(
(
writer.writerow(self.write_selling(selling))
for selling in kwargs["result"]
),
content_type="text/csv",
)
name = _("Sellings") + "_" + self.object.name + ".csv"
response["Content-Disposition"] = "filename=" + name
return response
......
......@@ -43,7 +43,7 @@
<div id="birthdays_content">
{% if user.is_subscribed %}
{# Cache request for 1 hour #}
{% cache 3600 birthdays %}
{% cache 3600 "birthdays" %}
<ul class="birthdays_year">
{% for d in birthdays.dates('date_of_birth', 'year', 'DESC') %}
<li>
......
......@@ -449,7 +449,7 @@ class WeekmailPreviewView(ComTabsMixin, CanEditPropMixin, DetailView):
)
except:
pass
return super(WeekmailEditView, self).get(request, *args, **kwargs)
return super(WeekmailPreviewView, self).get(request, *args, **kwargs)
def get_object(self, queryset=None):
return self.model.objects.filter(sent=False).order_by("-id").first()
......
......@@ -31,7 +31,7 @@ from django.conf import settings
class Command(BaseCommand):
"""
Compiles scss in static folder for production
Compiles scss in static folder for production
"""
help = "Compile scss files from static folder"
......
......@@ -1490,7 +1490,9 @@ class OperationLog(models.Model):
User, related_name="logs", on_delete=models.SET_NULL, null=True
)
operation_type = models.CharField(
_("operation type"), max_length=40, choices=settings.SITH_LOG_OPERATION_TYPE,
_("operation type"),
max_length=40,
choices=settings.SITH_LOG_OPERATION_TYPE,
)
def is_owned_by(self, user):
......
......@@ -33,16 +33,16 @@ from django.db import connection, migrations
class PsqlRunOnly(migrations.RunSQL):
"""
This is an SQL runner that will launch the given command only if
the used DBMS is PostgreSQL.
It may be useful to run Postgres' specific SQL, or to take actions
that would be non-senses with backends other than Postgre, such
as disabling particular constraints that would prevent the migration
to run successfully.
This is an SQL runner that will launch the given command only if
the used DBMS is PostgreSQL.
It may be useful to run Postgres' specific SQL, or to take actions
that would be non-senses with backends other than Postgre, such
as disabling particular constraints that would prevent the migration
to run successfully.
See `club/migrations/0010_auto_20170912_2028.py` as an example.
Some explanations can be found here too:
https://stackoverflow.com/questions/28429933/django-migrations-using-runpython-to-commit-changes
See `club/migrations/0010_auto_20170912_2028.py` as an example.
Some explanations can be found here too:
https://stackoverflow.com/questions/28429933/django-migrations-using-runpython-to-commit-changes
"""
def _run_sql(self, schema_editor, sqls):
......
......@@ -35,9 +35,9 @@ from core.scss.storage import ScssFileStorage, find_file
class ScssProcessor(object):
"""
If DEBUG mode enabled : compile the scss file
Else : give the path of the corresponding css supposed to already be compiled
Don't forget to use compilestatics to compile scss for production
If DEBUG mode enabled : compile the scss file
Else : give the path of the corresponding css supposed to already be compiled
Don't forget to use compilestatics to compile scss for production
"""
prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/"))
......
......@@ -59,7 +59,7 @@
</div>
<div id="header_bar">
<ul id="header_bars_infos">
{% cache 100 counters_activity %}
{% cache 100 "counters_activity" %}
{% for bar in Counter.objects.filter(type="BAR").all() %}
<li>
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
......
......@@ -113,10 +113,11 @@
{% endif %}
{% endmacro %}
{% macro paginate(page_obj, paginator) %}
{% macro paginate(page_obj, paginator, js_action) %}
{% set js = js_action|default('') %}
{% if page_obj.has_previous() or page_obj.has_next() %}
{% if page_obj.has_previous() %}
<a href="?page={{ page_obj.previous_page_number() }}">{% trans %}Previous{% endtrans %}</a>
<a {% if js %} type="submit" onclick="{{ js }}" {% endif %} href="?page={{ page_obj.previous_page_number() }}">{% trans %}Previous{% endtrans %}</a>
{% else %}