Skip to content
Browse files

[front] display promo code on group management page

  • Loading branch information
aktiur committed Nov 9, 2017
1 parent c72a810 commit 2048f74a8d28234752d6b765e758fa52a01b4386
@@ -382,3 +382,7 @@
# allow insecure transports for OAUTHLIB in DEBUG mode
os.environ.setdefault('OAUTHLIB_INSECURE_TRANSPORT', 'y')

# Get the promo
PROMO_CODE_KEY = os.environb.get(b'PROMO_CODE_KEY', b'prout')
PROMO_CODE_TAG = os.environ.get('PROMO_CODE_TAG', 'Groupe certifié')
@@ -33,6 +33,20 @@ <h6 class="subhead">Contact</h6>

{% if certified %}
<h3>Mon code promo pour ce mois-ci</h3>

<div style="text-align: center; margin: 2em auto;">
<span style="padding: 10px; font-weight: bolder; font-size: 2em; border: 2px solid darkgrey;">{{ group_promo_code }}</span>

Ce code peut être utilisé sur le <a href="">site d'achat de matériel</a>.

{% endif %}

<h3>Les animateurs et autres gestionnaires du groupe</h3>

<h4>Les animateurs du groupe</h4>
@@ -6,9 +6,11 @@
from django.contrib import messages
from django.http import Http404, HttpResponseForbidden, HttpResponseRedirect, HttpResponseBadRequest
from django.core.urlresolvers import reverse_lazy, reverse
from django.conf import settings

from groups.models import SupportGroup, Membership
from groups.tasks import send_someone_joined_notification
from groups.actions.promo_codes import get_next_promo_code

from ..forms import SupportGroupForm, AddReferentForm, AddManagerForm, GroupGeocodingForm
from ..view_mixins import (
@@ -110,17 +112,18 @@ def get_forms(self):

def get_context_data(self, **kwargs):
referents = self.object.memberships.filter(is_referent=True).order_by('created')
managers = self.object.memberships.filter(is_manager=True, is_referent=False).order_by('created')
members = self.object.memberships.all().order_by('created')
kwargs['referents'] = self.object.memberships.filter(is_referent=True).order_by('created')
kwargs['managers'] = self.object.memberships.filter(is_manager=True, is_referent=False).order_by('created')
kwargs['members'] = self.object.memberships.all().order_by('created')
kwargs['certified'] = self.object.tags.filter(label=settings.PROMO_CODE_TAG).exists()
if kwargs['certified']:
kwargs['group_promo_code'] = get_next_promo_code(self.object)

return super().get_context_data(
is_referent=self.user_membership is not None and self.user_membership.is_referent,
is_manager=self.user_membership is not None and (self.user_membership.is_referent or self.user_membership.is_manager),

def get(self, request, *args, **kwargs):
Empty file.
@@ -0,0 +1,95 @@
import struct
import base64
import hmac
import hashlib
from datetime import date

from django.conf import settings
from django.utils import timezone

REFERENCE_DATE = date(2017, 1, 1)
DIGESTMOD = hashlib.sha1
BASE64ENC = base64.urlsafe_b64encode

def generate_date_fragment(expiration_date):
"""Generate a two-character encoding of the expiration date
That encoding is done that way:
* Compute the number of days since the REFERENCE_DATE
* encode it as two bytes (low-endian)
* left shift the second byte by four bits
It should now look that way (second hexdigit of second byte is all zero after left shift) :
xxxx xxxx xxxx 0000
Only twelve bits of data ==> can be encoded by two base64 characters
* Encode it in base64 and keep the two first characters
:param expiration_date: a date corresponding to the day the promo code should expire
:return: base64 encoding of the input date (bytes object)
days = (expiration_date - REFERENCE_DATE).days
assert days < 4096 # 2^12 or the maximum value that can be set in 2 Base64 characters

# use little-endian for packing
b = bytearray(struct.pack('<I', days)[:2])
b[1] <<= 4

return BASE64ENC(b)[:2]

def generate_msg_part_for_group(group, expiration_date):
"""Generate the msg part of the promo code for a specific group
The msg part is made of :
* the expiration date (as 2 base64 characters)
* 6 characters corresponding to the support group id
:param group: the group for which the promo code must be generated
:param expiration_date: the expiration date for the promo code
date_fragment = generate_date_fragment(expiration_date)

# let's hash the group part to make sure it works whatever the uuid generation mode
# keep only the strictly minimum number of bytes
keep_bytes = (GROUP_ID_SIZE * 3 // 4) + 1
group_bytes = DIGESTMOD([:keep_bytes]

# let's use the first GROUP_ID_SIZE characters of the base64 encoding
group_fragment = BASE64ENC(group_bytes)[:GROUP_ID_SIZE]

return date_fragment + group_fragment

def sign_code(msg):
keep_bytes = (SIGNATURE_SIZE * 3 // 4) + 1
sig_bytes =

signature_frag = BASE64ENC(sig_bytes)[:SIGNATURE_SIZE]

return msg + signature_frag

def generate_code_for_group(group, expiration_date):
msg = generate_msg_part_for_group(group, expiration_date)
return sign_code(msg)

def get_next_promo_code(group):
today =

if today.month == 12:
expiration_date = date(today.year+1, 1, 1)
expiration_date = date(today.year, today.month+1, 1)

return generate_code_for_group(group, expiration_date)

0 comments on commit 2048f74

Please sign in to comment.