mirror of
https://github.com/ae-utbm/sith.git
synced 2024-12-22 07:41:14 +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
|
||||
dist/
|
||||
.vscode/
|
||||
.idea
|
||||
.idea/
|
||||
env/
|
||||
doc/html
|
||||
data/
|
||||
|
@ -33,6 +33,8 @@ from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
||||
from core.models import User
|
||||
from club.models import Club, Membership, Mailing
|
||||
from club.forms import MailingForm
|
||||
from sith.settings import SITH_BAR_MANAGER
|
||||
|
||||
|
||||
# Create your tests here.
|
||||
|
||||
@ -43,7 +45,7 @@ class ClubTest(TestCase):
|
||||
self.skia = User.objects.filter(username="skia").first()
|
||||
self.rbatsbak = User.objects.filter(username="rbatsbak").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):
|
||||
self.client.login(username="root", password="plop")
|
||||
@ -390,7 +392,7 @@ class MailingFormTest(TestCase):
|
||||
self.rbatsbak = User.objects.filter(username="rbatsbak").first()
|
||||
self.krophil = User.objects.filter(username="krophil").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(
|
||||
user=self.rbatsbak,
|
||||
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
|
||||
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
// 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;
|
||||
border-radius: 6px 6px 0 0;
|
||||
box-shadow: $shadow-color 0 0 15px;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
flex: auto;
|
||||
|
@ -166,14 +166,15 @@ $min_col_width: 100px;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
gap: $gap;
|
||||
|
||||
>.candidate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
list-style-type: none;
|
||||
width: 100%;
|
||||
gap: $gap;
|
||||
|
||||
>input[type="radio"]:checked + label,
|
||||
@ -186,6 +187,10 @@ $min_col_width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
>label {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
>label>figure,
|
||||
>figure {
|
||||
position: relative;
|
||||
@ -194,17 +199,22 @@ $min_col_width: 100px;
|
||||
align-items: center;
|
||||
gap: $gap;
|
||||
padding: 10px;
|
||||
max-width: 100%;
|
||||
|
||||
>img {
|
||||
max-width: 100%;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
>figcaption {
|
||||
width: 100%;
|
||||
max-width: inherit !important;
|
||||
overflow: hidden;
|
||||
|
||||
h5 {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
q {
|
||||
.candidate_program {
|
||||
margin: 5px 0;
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,10 @@ import json
|
||||
import re
|
||||
import typing
|
||||
|
||||
from urllib.parse import unquote
|
||||
from django.http import HttpRequest
|
||||
from django.utils.translation import gettext as _
|
||||
from sentry_sdk import capture_message
|
||||
|
||||
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 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 ("[]", ""):
|
||||
self.error_messages.add(_("You have no basket."))
|
||||
return
|
||||
|
||||
# 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):
|
||||
# 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."))
|
||||
return
|
||||
|
||||
try:
|
||||
basket = json.loads(basket)
|
||||
except json.JSONDecodeError:
|
||||
self.error_messages.add(_("The basket cookie was badly formatted."))
|
||||
return
|
||||
|
||||
if type(basket) is not list or len(basket) == 0:
|
||||
self.error_messages.add(_("Your basket is empty."))
|
||||
return
|
||||
|
||||
for item in basket:
|
||||
expected_keys = {"id", "quantity", "name", "unit_price"}
|
||||
if type(item) is not dict or set(item.keys()) != expected_keys:
|
||||
|
@ -63,7 +63,7 @@ document.addEventListener('alpine:init', () => {
|
||||
|
||||
edit_cookies() {
|
||||
// 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 json
|
||||
from datetime import datetime
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
from datetime import datetime
|
||||
from urllib.parse import unquote
|
||||
from OpenSSL import crypto
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@ -104,7 +105,7 @@ class EbouticCommand(TemplateView):
|
||||
request.session["basket_id"] = basket.id
|
||||
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"])
|
||||
ids = [item["id"] for item in items]
|
||||
quantities = [item["quantity"] for item in items]
|
||||
|
@ -100,7 +100,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<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) %}
|
||||
<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 '' }}>
|
||||
@ -118,7 +118,7 @@
|
||||
{%- endif %}
|
||||
</td>
|
||||
{%- 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">
|
||||
{%- for candidature in election_list.candidatures.filter(role=role) %}
|
||||
<li class="candidate">
|
||||
@ -141,7 +141,7 @@
|
||||
{%- endif %}
|
||||
</figcaption>
|
||||
{%- if user.can_edit(candidature) -%}
|
||||
{% if election.is_vote_editable %}
|
||||
{%- if election.is_vote_editable -%}
|
||||
<div class="edit_btns">
|
||||
<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>
|
||||
|
@ -278,8 +278,8 @@ SITH_MAIN_CLUB = {
|
||||
|
||||
# Bar managers
|
||||
SITH_BAR_MANAGER = {
|
||||
"name": "BdF",
|
||||
"unix_name": "bdf",
|
||||
"name": "Pdf",
|
||||
"unix_name": "pdfesti",
|
||||
"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_OFFICES = {2: "BdF", 1: "AE"}
|
||||
SITH_COUNTER_OFFICES = {2: "PdF", 1: "AE"}
|
||||
|
||||
SITH_COUNTER_PAYMENT_METHOD = [
|
||||
("CHECK", _("Check")),
|
||||
|
Loading…
Reference in New Issue
Block a user