mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 15:51:19 +00:00
Fix le panier de l'Eboutic pour Safari (#518)
Co-authored-by: Théo DURR <git@theodurr.fr> Co-authored-by: thomas girod <56346771+imperosol@users.noreply.github.com>
This commit is contained in:
parent
1d82e2a7d9
commit
faccc1367f
2
.gitignore
vendored
2
.gitignore
vendored
@ -7,7 +7,7 @@ db.sqlite3
|
|||||||
pyrightconfig.json
|
pyrightconfig.json
|
||||||
dist/
|
dist/
|
||||||
.vscode/
|
.vscode/
|
||||||
.idea
|
.idea/
|
||||||
env/
|
env/
|
||||||
doc/html
|
doc/html
|
||||||
data/
|
data/
|
||||||
|
@ -33,6 +33,8 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
|||||||
from core.models import User
|
from core.models import User
|
||||||
from club.models import Club, Membership, Mailing
|
from club.models import Club, Membership, Mailing
|
||||||
from club.forms import MailingForm
|
from club.forms import MailingForm
|
||||||
|
from sith.settings import SITH_BAR_MANAGER
|
||||||
|
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
|
||||||
@ -43,7 +45,7 @@ class ClubTest(TestCase):
|
|||||||
self.skia = User.objects.filter(username="skia").first()
|
self.skia = User.objects.filter(username="skia").first()
|
||||||
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
||||||
self.guy = User.objects.filter(username="guy").first()
|
self.guy = User.objects.filter(username="guy").first()
|
||||||
self.bdf = Club.objects.filter(unix_name="bdf").first()
|
self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first()
|
||||||
|
|
||||||
def test_create_add_user_to_club_from_root_ok(self):
|
def test_create_add_user_to_club_from_root_ok(self):
|
||||||
self.client.login(username="root", password="plop")
|
self.client.login(username="root", password="plop")
|
||||||
@ -390,7 +392,7 @@ class MailingFormTest(TestCase):
|
|||||||
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
||||||
self.krophil = User.objects.filter(username="krophil").first()
|
self.krophil = User.objects.filter(username="krophil").first()
|
||||||
self.comunity = User.objects.filter(username="comunity").first()
|
self.comunity = User.objects.filter(username="comunity").first()
|
||||||
self.bdf = Club.objects.filter(unix_name="bdf").first()
|
self.bdf = Club.objects.filter(unix_name=SITH_BAR_MANAGER["unix_name"]).first()
|
||||||
Membership(
|
Membership(
|
||||||
user=self.rbatsbak,
|
user=self.rbatsbak,
|
||||||
club=self.bdf,
|
club=self.bdf,
|
||||||
|
2
core/static/core/js/shorten.min.js
vendored
2
core/static/core/js/shorten.min.js
vendored
@ -19,4 +19,4 @@
|
|||||||
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),e(this).data("jquery.shorten")&&!t.force?!1:(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({height:"0%"},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({height:"100%"},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html(),r=s.text().length;if(r>t.showChars+t.minHideChars){var o=n.substr(0,t.showChars);if(o.indexOf("<")>=0){for(var a=!1,i="",h=0,l=[],c=null,f=0,u=0;u<=t.showChars;f++)if("<"!=n[f]||a||(a=!0,c=n.substring(f+1,n.indexOf(">",f)),"/"==c[0]?c!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=c.toLowerCase()&&l.unshift(c)),a&&">"==n[f]&&(a=!1),a)i+=n.charAt(f);else if(u++,h<=t.showChars)i+=n.charAt(f),h++;else if(l.length>0){for(j=0;j<l.length;j++)i+="</"+l[j]+">";break}o=e("<div/>").html(i+'<span class="ellip">'+t.ellipsesText+"</span>").html()}else o+=t.ellipsesText;var m='<div class="shortcontent">'+o+'</div><div class="allcontent">'+n+'</div><span><a href="javascript://nop/" class="morelink">'+t.moreText+"</a></span>";s.html(m),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery);
|
!function(e){e.fn.shorten=function(s){"use strict";var t={showChars:100,minHideChars:10,ellipsesText:"...",moreText:"more",lessText:"less",onLess:function(){},onMore:function(){},errMsg:null,force:!1};return s&&e.extend(t,s),(!e(this).data("jquery.shorten")||!!t.force)&&(e(this).data("jquery.shorten",!0),e(document).off("click",".morelink"),e(document).on({click:function(){var s=e(this);return s.hasClass("less")?(s.removeClass("less"),s.html(t.moreText),s.parent().prev().animate({},function(){s.parent().prev().prev().show()}).hide("fast",function(){t.onLess()})):(s.addClass("less"),s.html(t.lessText),s.parent().prev().animate({},function(){s.parent().prev().prev().hide()}).show("fast",function(){t.onMore()})),!1}},".morelink"),this.each(function(){var s=e(this),n=s.html();if(s.text().length>t.showChars+t.minHideChars){var r=n.substr(0,t.showChars);if(r.indexOf("<")>=0){for(var a=!1,o="",i=0,l=[],h=null,c=0,f=0;f<=t.showChars;c++)if("<"!=n[c]||a||(a=!0,"/"==(h=n.substring(c+1,n.indexOf(">",c)))[0]?h!="/"+l[0]?t.errMsg="ERROR en HTML: the top of the stack should be the tag that closes":l.shift():"br"!=h.toLowerCase()&&l.unshift(h)),a&&">"==n[c]&&(a=!1),a)o+=n.charAt(c);else if(f++,i<=t.showChars)o+=n.charAt(c),i++;else if(l.length>0){for(j=0;j<l.length;j++)o+="</"+l[j]+">";break}r=e("<div/>").html(o+'<span class="ellip">'+t.ellipsesText+"</span>").html()}else r+=t.ellipsesText;var p='<div class="shortcontent">'+r+'</div><div class="allcontent">'+n+'</div><span><a href="javascript://nop/" class="morelink">'+t.moreText+"</a></span>";s.html(p),s.find(".allcontent").hide(),e(".shortcontent p:last",s).css("margin-bottom",0)}}))}}(jQuery);
|
@ -381,6 +381,7 @@ header {
|
|||||||
color: $white-color;
|
color: $white-color;
|
||||||
border-radius: 6px 6px 0 0;
|
border-radius: 6px 6px 0 0;
|
||||||
box-shadow: $shadow-color 0 0 15px;
|
box-shadow: $shadow-color 0 0 15px;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
flex: auto;
|
flex: auto;
|
||||||
|
@ -166,14 +166,15 @@ $min_col_width: 100px;
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
>.candidate {
|
>.candidate {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
|
width: 100%;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
>input[type="radio"]:checked + label,
|
>input[type="radio"]:checked + label,
|
||||||
@ -186,6 +187,10 @@ $min_col_width: 100px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
>label {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
>label>figure,
|
>label>figure,
|
||||||
>figure {
|
>figure {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -194,17 +199,22 @@ $min_col_width: 100px;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
max-width: 100%;
|
||||||
|
|
||||||
>img {
|
>img {
|
||||||
max-width: 100%;
|
max-width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
>figcaption {
|
>figcaption {
|
||||||
|
width: 100%;
|
||||||
|
max-width: inherit !important;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
q {
|
.candidate_program {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,10 @@ import json
|
|||||||
import re
|
import re
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
|
from urllib.parse import unquote
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
from sentry_sdk import capture_message
|
||||||
|
|
||||||
from eboutic.models import get_eboutic_products
|
from eboutic.models import get_eboutic_products
|
||||||
|
|
||||||
@ -97,23 +99,34 @@ class BasketForm:
|
|||||||
- all the ids refer to products the user is allowed to buy
|
- all the ids refer to products the user is allowed to buy
|
||||||
- all the quantities are positive integers
|
- all the quantities are positive integers
|
||||||
"""
|
"""
|
||||||
basket = self.cookies.get("basket_items", None)
|
basket = unquote(self.cookies.get("basket_items", None))
|
||||||
if basket is None or basket in ("[]", ""):
|
if basket is None or basket in ("[]", ""):
|
||||||
self.error_messages.add(_("You have no basket."))
|
self.error_messages.add(_("You have no basket."))
|
||||||
return
|
return
|
||||||
|
|
||||||
# check that the json is not nested before parsing it to make sure
|
# check that the json is not nested before parsing it to make sure
|
||||||
# malicious user can't ddos the server with deeply nested json
|
# malicious user can't DDoS the server with deeply nested json
|
||||||
if not BasketForm.json_cookie_re.match(basket):
|
if not BasketForm.json_cookie_re.match(basket):
|
||||||
|
# As the validation of the cookie goes through a rather boring regex,
|
||||||
|
# we can regularly have to deal with subtle errors that we hadn't forecasted,
|
||||||
|
# so we explicitly lay a Sentry message capture here.
|
||||||
|
capture_message(
|
||||||
|
"Eboutic basket regex checking failed to validate basket json",
|
||||||
|
level="error",
|
||||||
|
)
|
||||||
self.error_messages.add(_("The request was badly formatted."))
|
self.error_messages.add(_("The request was badly formatted."))
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
basket = json.loads(basket)
|
basket = json.loads(basket)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
self.error_messages.add(_("The basket cookie was badly formatted."))
|
self.error_messages.add(_("The basket cookie was badly formatted."))
|
||||||
return
|
return
|
||||||
|
|
||||||
if type(basket) is not list or len(basket) == 0:
|
if type(basket) is not list or len(basket) == 0:
|
||||||
self.error_messages.add(_("Your basket is empty."))
|
self.error_messages.add(_("Your basket is empty."))
|
||||||
return
|
return
|
||||||
|
|
||||||
for item in basket:
|
for item in basket:
|
||||||
expected_keys = {"id", "quantity", "name", "unit_price"}
|
expected_keys = {"id", "quantity", "name", "unit_price"}
|
||||||
if type(item) is not dict or set(item.keys()) != expected_keys:
|
if type(item) is not dict or set(item.keys()) != expected_keys:
|
||||||
|
@ -63,7 +63,7 @@ document.addEventListener('alpine:init', () => {
|
|||||||
|
|
||||||
edit_cookies() {
|
edit_cookies() {
|
||||||
// a cookie survives an hour
|
// a cookie survives an hour
|
||||||
document.cookie = "basket_items=" + JSON.stringify(this.items) + ";Max-Age=3600";
|
document.cookie = "basket_items=" + encodeURIComponent(JSON.stringify(this.items)) + ";Max-Age=3600";
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from urllib.parse import unquote
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
@ -104,7 +105,7 @@ class EbouticCommand(TemplateView):
|
|||||||
request.session["basket_id"] = basket.id
|
request.session["basket_id"] = basket.id
|
||||||
request.session.modified = True
|
request.session.modified = True
|
||||||
|
|
||||||
items = json.loads(request.COOKIES["basket_items"])
|
items = json.loads(unquote(request.COOKIES["basket_items"]))
|
||||||
items.sort(key=lambda item: item["id"])
|
items.sort(key=lambda item: item["id"])
|
||||||
ids = [item["id"] for item in items]
|
ids = [item["id"] for item in items]
|
||||||
quantities = [item["quantity"] for item in items]
|
quantities = [item["quantity"] for item in items]
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="role_candidates">
|
<tr class="role_candidates">
|
||||||
<td class="list_per_role" style="width: {{ 100 / (election_lists.count() + 1) }}%">
|
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists.count() + 1) }}%">
|
||||||
{%- if role.max_choice == 1 and election.can_vote(user) %}
|
{%- if role.max_choice == 1 and election.can_vote(user) %}
|
||||||
<div class="radio-btn">
|
<div class="radio-btn">
|
||||||
<input id="id_{{ role.title }}_{{ count[0] }}" type="radio" name="{{ role.title }}" value {{ '' if role_data in election_form else 'checked' }} {{ 'disabled' if election.has_voted(user) else '' }}>
|
<input id="id_{{ role.title }}_{{ count[0] }}" type="radio" name="{{ role.title }}" value {{ '' if role_data in election_form else 'checked' }} {{ 'disabled' if election.has_voted(user) else '' }}>
|
||||||
@ -118,7 +118,7 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
</td>
|
</td>
|
||||||
{%- for election_list in election_lists %}
|
{%- for election_list in election_lists %}
|
||||||
<td class="list_per_role" style="width: {{ 100 / (election_lists.count() + 1) }}%">
|
<td class="list_per_role" style="width: 100%; max-width: {{ 100 / (election_lists.count() + 1) }}%">
|
||||||
<ul class="candidates">
|
<ul class="candidates">
|
||||||
{%- for candidature in election_list.candidatures.filter(role=role) %}
|
{%- for candidature in election_list.candidatures.filter(role=role) %}
|
||||||
<li class="candidate">
|
<li class="candidate">
|
||||||
@ -141,7 +141,7 @@
|
|||||||
{%- endif %}
|
{%- endif %}
|
||||||
</figcaption>
|
</figcaption>
|
||||||
{%- if user.can_edit(candidature) -%}
|
{%- if user.can_edit(candidature) -%}
|
||||||
{% if election.is_vote_editable %}
|
{%- if election.is_vote_editable -%}
|
||||||
<div class="edit_btns">
|
<div class="edit_btns">
|
||||||
<a href="{{url('election:update_candidate', candidature_id=candidature.id)}}">{% trans %}✏️{% endtrans %}</a>
|
<a href="{{url('election:update_candidate', candidature_id=candidature.id)}}">{% trans %}✏️{% endtrans %}</a>
|
||||||
<a href="{{url('election:delete_candidate', candidature_id=candidature.id)}}">{% trans %}❌{% endtrans %}</a>
|
<a href="{{url('election:delete_candidate', candidature_id=candidature.id)}}">{% trans %}❌{% endtrans %}</a>
|
||||||
|
@ -278,8 +278,8 @@ SITH_MAIN_CLUB = {
|
|||||||
|
|
||||||
# Bar managers
|
# Bar managers
|
||||||
SITH_BAR_MANAGER = {
|
SITH_BAR_MANAGER = {
|
||||||
"name": "BdF",
|
"name": "Pdf",
|
||||||
"unix_name": "bdf",
|
"unix_name": "pdfesti",
|
||||||
"address": "6 Boulevard Anatole France, 90000 Belfort",
|
"address": "6 Boulevard Anatole France, 90000 Belfort",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,7 +376,7 @@ SITH_SUBSCRIPTION_LOCATIONS = [
|
|||||||
|
|
||||||
SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")]
|
SITH_COUNTER_BARS = [(1, "MDE"), (2, "Foyer"), (35, "La Gommette")]
|
||||||
|
|
||||||
SITH_COUNTER_OFFICES = {2: "BdF", 1: "AE"}
|
SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"}
|
||||||
|
|
||||||
SITH_COUNTER_PAYMENT_METHOD = [
|
SITH_COUNTER_PAYMENT_METHOD = [
|
||||||
("CHECK", _("Check")),
|
("CHECK", _("Check")),
|
||||||
|
Loading…
Reference in New Issue
Block a user