Merge pull request #477 from imperosol/eboutic

Refonte de la boutique en ligne
This commit is contained in:
thomas girod
2022-10-31 16:28:56 +01:00
committed by GitHub
23 changed files with 1514 additions and 761 deletions

View File

@ -505,6 +505,8 @@ Welcome to the wiki page!
refound.save()
# Counters
subscribers = Group.objects.get(name="Subscribers")
old_subscribers = Group.objects.get(name="Old subscribers")
Customer(user=skia, account_id="6568j", amount=0).save()
Customer(user=r, account_id="4000k", amount=0).save()
p = ProductType(name="Bières bouteilles")
@ -525,6 +527,9 @@ Welcome to the wiki page!
club=main_club,
)
cotis.save()
cotis.buying_groups.add(subscribers)
cotis.buying_groups.add(old_subscribers)
cotis.save()
cotis2 = Product(
name="Cotis 2 semestres",
code="2SCOTIZ",
@ -535,6 +540,9 @@ Welcome to the wiki page!
club=main_club,
)
cotis2.save()
cotis2.buying_groups.add(subscribers)
cotis2.buying_groups.add(old_subscribers)
cotis2.save()
refill = Product(
name="Rechargement 15 €",
code="15REFILL",
@ -545,6 +553,8 @@ Welcome to the wiki page!
club=main_club,
)
refill.save()
refill.buying_groups.add(subscribers)
refill.save()
barb = Product(
name="Barbar",
code="BARB",
@ -553,8 +563,11 @@ Welcome to the wiki page!
selling_price="1.7",
special_selling_price="1.6",
club=main_club,
limit_age=18,
)
barb.save()
barb.buying_groups.add(subscribers)
barb.save()
cble = Product(
name="Chimay Bleue",
code="CBLE",
@ -563,8 +576,11 @@ Welcome to the wiki page!
selling_price="1.7",
special_selling_price="1.6",
club=main_club,
limit_age=18,
)
cble.save()
cble.buying_groups.add(subscribers)
cble.save()
cons = Product(
name="Consigne Eco-cup",
code="CONS",
@ -574,7 +590,6 @@ Welcome to the wiki page!
special_selling_price="1",
club=main_club,
)
cons.id = 1152
cons.save()
dcons = Product(
name="Déconsigne Eco-cup",
@ -585,9 +600,8 @@ Welcome to the wiki page!
special_selling_price="-1",
club=main_club,
)
dcons.id = 1151
dcons.save()
Product(
cors = Product(
name="Corsendonk",
code="CORS",
product_type=p,
@ -595,8 +609,12 @@ Welcome to the wiki page!
selling_price="1.7",
special_selling_price="1.6",
club=main_club,
).save()
Product(
limit_age=18,
)
cors.save()
cors.buying_groups.add(subscribers)
cors.save()
carolus = Product(
name="Carolus",
code="CARO",
product_type=p,
@ -604,7 +622,11 @@ Welcome to the wiki page!
selling_price="1.7",
special_selling_price="1.6",
club=main_club,
).save()
limit_age=18,
)
carolus.save()
carolus.buying_groups.add(subscribers)
carolus.save()
mde = Counter.objects.filter(name="MDE").first()
mde.products.add(barb)
mde.products.add(cble)

View File

@ -325,6 +325,13 @@ class User(AbstractBaseUser):
)
return s.exists()
@cached_property
def account_balance(self):
if hasattr(self, "customer"):
return self.customer.amount
else:
return 0
_club_memberships = {}
_group_names = {}
_group_ids = {}
@ -459,6 +466,20 @@ class User(AbstractBaseUser):
def is_banned_counter(self):
return self.is_in_group(settings.SITH_GROUP_BANNED_COUNTER_ID)
@cached_property
def age(self) -> int:
"""
Return the age this user has the day the method is called.
"""
today = timezone.now()
age = today.year - self.date_of_birth.year
# remove a year if this year's birthday is yet to come
age -= (today.month, today.day) < (
self.date_of_birth.month,
self.date_of_birth.day,
)
return age
def save(self, *args, **kwargs):
create = False
with transaction.atomic():

5
core/static/core/js/alpinejs.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -159,9 +159,9 @@ a {
header {
width: 90%;
margin: 0px auto;
margin: 0 auto;
display: flex;
box-shadow: $shadow-color 0px 0px 15px;
box-shadow: $shadow-color 0 0 15px;
border-top: none;
//background-color: $primary-neutral-dark-color;
border-radius: 0px 0px 10px 10px;
@ -196,7 +196,7 @@ header {
}
#header_connect_links {
margin: 0.6em 0.6em 0em auto;
margin: 0.6em 0.6em 0 auto;
padding: 0.2em;
color: $white-color;
form {
@ -221,7 +221,7 @@ header {
a {
text-decoration: none;
margin: 0em 1em;
margin: 0 1em;
font-weight: bold;
color: $white-color;
&:hover {
@ -247,7 +247,7 @@ header {
#header_search {
display: inline-block;
flex: auto;
margin: 0.8em 0em;
margin: 0.8em 0;
input {
width: 14ch;
}
@ -258,10 +258,10 @@ header {
flex: initial;
flex-wrap: wrap;
text-align: right;
margin: 0em;
margin: 0;
div {
display: inline;
padding: 1.2em 0em;
padding: 1.2em 0;
&:first-child {
flex: auto;
}
@ -283,7 +283,7 @@ header {
background: white;
text-align: left;
font-size: 80%;
margin: 1.5em 0em 0em -14em;
margin: 1.5em 0 0em -14em;
.header_notif_date {
font-weight: bold;
}
@ -291,7 +291,7 @@ header {
color: grey;
}
a {
margin: 0em;
margin: 0;
color: $black-color;
&:hover {
color: $primary-dark-color;
@ -319,7 +319,7 @@ header {
#popupheader {
width: 88%;
margin: 0px auto;
margin: 0 auto;
padding: 0.3em 1%;
}
@ -362,15 +362,15 @@ header {
#page {
width: 90%;
margin: 0em auto;
/*---------------------------------NAV---------------------------------*/
margin: 20px auto 0;
/*---------------------------------NAV---------------------------------*/
nav {
display: flex;
flex-wrap: wrap;
background-color: $primary-dark-color;
color: $white-color;
border-radius: 6px 6px 0px 0px;
box-shadow: $shadow-color 0px 0px 15px;
border-radius: 6px 6px 0 0;
box-shadow: $shadow-color 0 0 15px;
a {
flex: auto;
@ -385,10 +385,10 @@ header {
background: $secondary-neutral-color;
color: $white-color;
&:first-of-type {
border-radius: 6px 0px 0px 0px;
border-radius: 6px 0 0 0;
}
&:last-of-type {
border-radius: 0px 6px 0px 0px;
border-radius: 0 6px 0 0;
}
}
}
@ -411,7 +411,7 @@ header {
overflow: auto;
width: 100%;
background-color: #f9f9f9;
box-shadow: 3px 3px 3px 0px $shadow-color;
box-shadow: 3px 3px 3px 0 $shadow-color;
z-index: 1;
}
@ -436,7 +436,7 @@ header {
/*--------------------------------CONTENT------------------------------*/
#quick_notif {
width: 100%;
margin: 0px auto;
margin: 0 auto;
list-style-type: none;
background: $second-color;
li {
@ -447,7 +447,7 @@ header {
#content {
padding: 1em 1%;
box-shadow: $shadow-color 0px 5px 10px;
box-shadow: $shadow-color 0 5px 10px;
background: $white-color;
overflow: auto;
}
@ -492,7 +492,7 @@ header {
flex-wrap: wrap;
.news_column {
display: inline-block;
margin: 0px;
margin: 0;
vertical-align: top;
}
#news_admin {
@ -533,7 +533,7 @@ header {
margin-bottom: 1em;
#agenda_title,#birthdays_title {
margin: 0em;
border-radius: 5px 5px 0px 0px;
border-radius: 5px 5px 0 0;
box-shadow: $shadow-color 1px 1px 1px;
padding: 0.5em;
font-weight: bold;
@ -602,10 +602,10 @@ header {
font-weight: bold;
font-family: monospace;
font-size: 1.4em;
border-radius: 7px 0px 0px 7px;
border-radius: 7px 0 0 7px;
div {
margin: 0px auto;
margin: 0 auto;
.day {
font-size: 1.5em;
}
@ -690,7 +690,7 @@ header {
padding: 0.4em;
padding-left: 1em;
background: $secondary-neutral-light-color;
box-shadow: $shadow-color 0px 0px 2px;
box-shadow: $shadow-color 0 0 2px;
border-radius: 18px 5px 18px 5px;
h4 {
margin: 0em;
@ -1023,7 +1023,7 @@ h1, h2, h3, h4, h5, h6 {
h1 {
font-size: 160%;
margin-left: 0px;
margin-left: 0;
}
h2 {
@ -1053,7 +1053,7 @@ h6 {
p, pre {
margin-top: 0.8em;
margin-left: 0px;
margin-left: 0;
}
ul, ol, dl {
@ -1111,7 +1111,7 @@ td {
overflow: hidden;
text-overflow: ellipsis;
> ul {
margin-top: 0px;
margin-top: 0;
}
}
@ -1464,7 +1464,7 @@ textarea {
}
.search_bar {
margin: 10px 0px;
margin: 10px 0;
display: flex;
flex-wrap: wrap;
height: 20p;
@ -1480,7 +1480,7 @@ textarea {
margin-top: 5px;
background: $secondary-color;
color: white;
border-radius: 10px 10px 0px 0px;
border-radius: 10px 10px 0 0;
.title {
text-transform: uppercase;
}
@ -1520,7 +1520,7 @@ textarea {
text-align: center;
img {
max-width: 70%;
margin: 0px auto;
margin: 0 auto;
}
}
@ -1608,7 +1608,7 @@ footer {
display: flex;
flex-wrap: wrap;
background-color: $primary-neutral-dark-color;
box-shadow: $shadow-color 0px 0px 15px;
box-shadow: $shadow-color 0 0 15px;
a {
padding: 0.8em;
flex: 1;
@ -1624,7 +1624,7 @@ footer {
/*---------------------------------FORMS-------------------------------*/
form {
margin: 0px auto;
margin: 0 auto;
margin-bottom: 10px;
}
@ -1638,7 +1638,7 @@ label {
}
.ui-dialog .ui-dialog-buttonpane {
bottom: 0px;
bottom: 0;
position: absolute;
width: 97%;
}
@ -1683,8 +1683,8 @@ label {
/*-------------------------------MARKDOWN------------------------------*/
.markdown {
margin: 0px;
padding: 0px;
margin: 0;
padding: 0;
code {
font-family: monospace;
color: $white-color;
@ -1715,7 +1715,7 @@ label {
}
.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-top,
.ui-corner-left {
border-radius: 0px;
border-radius: 0;
}
#club_detail {
@ -1741,13 +1741,13 @@ $pedagogy-white-text: #f0f0f0;
.pedagogy {
&.star-not-checked {
color : #f7f7f7;
margin-bottom: 0px;
margin-top: 0px;
margin-bottom: 0;
margin-top: 0;
}
&.star-checked {
color: $pedagogy-orange;
margin-bottom: 0px;
margin-top: 0px;
margin-bottom: 0;
margin-top: 0;
}
&.grade-without-star {
@ -2119,8 +2119,8 @@ $pedagogy-white-text: #f0f0f0;
grid-area: markdown;
min-height: 139px;
margin-top: 0px;
margin-right: 0px;
margin-top: 0;
margin-right: 0;
padding: 10px;
text-align: justify;
overflow: auto;
@ -2166,7 +2166,7 @@ $pedagogy-white-text: #f0f0f0;
"report"
"date"
"author";
margin-top: 0px;
margin-top: 0;
text-align: center;
}
@ -2181,7 +2181,7 @@ $pedagogy-white-text: #f0f0f0;
@media screen and (max-width: $large-devices){
clip-path: none;
padding: 0px;
padding: 0;
padding-bottom: 7px;
}

View File

@ -0,0 +1,191 @@
#eboutic {
display: flex;
flex-direction: row-reverse;
align-items: flex-start;
column-gap: 20px;
margin: 0 20px 20px;
}
#eboutic-title {
margin-left: 20px;
}
#eboutic h3 {
margin-left: 0;
margin-right: 0;
}
#basket {
width: 300px;
border-radius: 8px;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
padding: 10px;
}
#basket h3 {
margin-top: 0;
}
@media screen and (max-width: 765px) {
#eboutic {
flex-direction: column;
align-items: center;
margin: 10px;
row-gap: 20px;
}
#eboutic-title {
margin-bottom: 20px;
margin-top: 4px;
}
#basket {
}
}
#eboutic #basket .error-message {
margin-top: 5px;
background-color: #f8d7da;
border: #f5c6cb 1px solid;
border-radius: 4px;
padding: 10px;
display: flex;
flex-direction: column;
row-gap: 7px;
}
#eboutic #basket .error-message p {
margin: 0;
}
#eboutic .item-list {
margin-left: 0;
list-style: none;
}
#eboutic .item-list li {
display: flex;
align-items: center;
margin-bottom: 10px
}
#eboutic .item-name {
flex: 1;
}
#eboutic .item-price, #eboutic .item-quantity {
width: 65px;
}
#eboutic .item-quantity {
text-align: center;
}
#eboutic .fa-plus, #eboutic .fa-minus {
cursor: pointer;
}
#eboutic .item-price {
text-align: right;
}
/* CSS du catalogue */
#eboutic #catalog {
display: flex;
flex-grow: 1;
flex-direction: column;
row-gap: 30px;
}
#eboutic .category-header {
margin-bottom: 15px;
}
#eboutic .product-group {
display: flex;
flex-wrap: wrap;
column-gap: 15px;
row-gap: 15px;
}
@media screen and (max-width: 765px) {
#eboutic #catalog {
row-gap: 15px;
}
#eboutic section {
text-align: center;
}
#eboutic .product-group {
justify-content: space-around;
}
}
#eboutic .product-button {
position: relative;
box-sizing: border-box;
min-width: 120px;
max-width: 150px;
padding: 10px;
overflow: hidden;
box-shadow: rgb(60 64 67 / 30%) 0 1px 3px 0, rgb(60 64 67 / 15%) 0 4px 8px 3px;
display: flex;
flex-direction: column;
align-items: center;
row-gap: 5px;
}
#eboutic .product-button:active {
box-shadow: none;
}
#eboutic .product-button img, #eboutic .product-button .fa {
margin: 0;
border-radius: 4px;
height: 40px;
line-height: 40px;
}
#eboutic .product-button p {
font-size: 13px;
margin: 0;
}
#eboutic .catalog-buttons {
display: flex;
justify-content: center;
column-gap: 30px;
margin: 30px 0 0;
}
#eboutic input {
all: unset;
}
#eboutic .catalog-buttons button {
font-size: 15px;
font-weight: normal;
color: white;
min-width: 60px;
padding: 5px 10px;
}
#eboutic .catalog-buttons .validate {
background-color: #354a5f;
}
#eboutic .catalog-buttons .clear {
background-color: gray;
}
#eboutic .catalog-buttons button i {
margin-right: 4px;
}
#eboutic .catalog-buttons button.validate:hover {
background-color: #2c3646;
}
#eboutic .catalog-buttons button.clear:hover {
background-color:hsl(210,5%,30%);
}
#eboutic .catalog-buttons form {
margin: 0;
}

View File

@ -0,0 +1,104 @@
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function get_starting_items() {
const cookie = getCookie("basket_items")
try {
// django cookie backend does an utter mess on non-trivial data types
// so we must perform a conversion of our own
const biscuit = JSON.parse(JSON.parse(cookie.replace(/\\054/g, ',')));
if (Array.isArray(biscuit)) {
return biscuit;
}
return [];
} catch (e) {
return [];
}
}
document.addEventListener('alpine:init', () => {
Alpine.data('basket', () => ({
items: get_starting_items(),
get_total() {
let total = 0;
for (const item of this.items) {
total += item["quantity"] * item["unit_price"];
}
return total;
},
add(item) {
item.quantity++;
this.edit_cookies()
},
remove(item_id) {
const index = this.items.findIndex(e => e.id === item_id);
if (index < 0) return;
this.items[index].quantity -= 1;
if (this.items[index].quantity === 0) {
this.items = this.items.filter((e) => e.id !== this.items[index].id);
}
this.edit_cookies();
},
clear_basket() {
this.items = []
this.edit_cookies();
},
edit_cookies() {
// a cookie survives an hour
document.cookie = "basket_items=" + JSON.stringify(this.items) + ";Max-Age=3600";
},
/**
* Create an item in the basket if it was not already in
* @param id : int the id of the product to add
* @param name : String the name of the product
* @param price : number the unit price of the product
*/
create_item(id, name, price) {
let new_item = {
id: id,
name: name,
quantity: 0,
unit_price: price
};
this.items.push(new_item);
this.add(new_item);
},
/**
* add an item to the basket.
* This is called when the user click
* on a button in the catalog (left side of the page)
* @param id : int the id of the product to add
* @param name : String the name of the product
* @param price : number the unit price of the product
*/
add_from_catalog(id, name, price) {
const item = this.items.find(e => e.id === id)
if (item === undefined) {
this.create_item(id, name, price);
} else {
// the user clicked on an item which is already in the basket
this.add(item);
}
},
}))
})

View File

@ -9,7 +9,10 @@
<link rel="stylesheet" href="{{ static('core/jquery.datetimepicker.min.css') }}">
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
<link rel="stylesheet" href="{{ scss('core/style.scss') }}">
<link rel="stylesheet" href="{{ static('core/js/ui/jquery-ui.min.css') }}">
{% block jquery_css %}
{# Thile file is quite heavy (around 250kb), so declaring it in a block allows easy removal #}
<link rel="stylesheet" href="{{ static('core/js/ui/jquery-ui.min.css') }}">
{% endblock %}
<link rel="preload" as="style" href="{{ static('core/font-awesome/css/font-awesome.min.css') }}" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="{{ static('core/font-awesome/css/font-awesome.min.css') }}"></noscript>
<script defer href="{{ static('core/font-awesome/js/fontawesone.min.js') }}"></script>
@ -18,6 +21,8 @@
<script src="{{ static('core/js/jquery-3.1.0.min.js') }}"></script>
<!-- Put here to always have acces to those functions on django widgets -->
<script src="{{ static('core/js/script.js') }}"></script>
{% block additional_css %}{% endblock %}
{% block additional_js %}{% endblock %}
{% endblock %}
</head>
@ -179,7 +184,9 @@
</div>
<a href="{{ url('forum:main') }}">{% trans %}Forum{% endtrans %}</a>
<a href="{{ url('sas:main') }}">{% trans %}Gallery{% endtrans %}</a>
<a href="{{ url('eboutic:main') }}">{% trans %}Eboutic{% endtrans %}</a>
{% if request.user.is_authenticated %}
<a href="{{ url('eboutic:main') }}">{% trans %}Eboutic{% endtrans %}</a>
{% endif %}
<div class="dropdown">
<button class="dropbtn">{% trans %}Services{% endtrans %}
<i class="fa fa-caret-down"></i>