From e726ff21a8ffb58e00c3bc376528d77e5a9a7a5b 2018-01-06 00:38:06 From: James Polley Date: 2018-01-06 00:38:06 Subject: [PATCH] Create regidesk app Shows summary of all attendees with a paid ticket, including boarding_pass status. Currently, regidesk allows staff with the requisite permission the ability to view the checkin status of attendees, and email the user their boarding pass email. Included is a view for the user to retrieve their own QR code (in case they got the plain-text version of the email, they can use this to download an image to their phone for faster checkin) --- diff --git a/pinaxcon/settings.py b/pinaxcon/settings.py index e6ae9d67ba74be32e9697218a6c3318cc44662d1..f8e62e154b0f6dffcbbbcbf971d21a2c1327210e 100644 --- a/pinaxcon/settings.py +++ b/pinaxcon/settings.py @@ -209,11 +209,14 @@ INSTALLED_APPS = [ # Registrasion "registrasion", - # Registrasion-stipe + # Registrasion-stripe "pinax.stripe", "django_countries", "registripe", + #registrasion-desk + "regidesk", + # admin - required by registrasion ?? "nested_admin", diff --git a/pinaxcon/urls.py b/pinaxcon/urls.py index dffceb8885dafb9743fd673cafb680014022be00..f822501ba0332daf0c5fd53e993658825e8767bd 100644 --- a/pinaxcon/urls.py +++ b/pinaxcon/urls.py @@ -26,7 +26,7 @@ urlpatterns = [ url(r'^tickets/payments/', include('registripe.urls')), url(r'^tickets/', include('registrasion.urls')), url(r'^nested_admin/', include('nested_admin.urls')), - + url(r'^checkin/', include('regidesk.urls')), url(r'^pages/', include('django.contrib.flatpages.urls')), url(r'^dashboard/', RedirectView.as_view(url='/')), diff --git a/vendor/regidesk/MANIFEST.in b/vendor/regidesk/MANIFEST.in new file mode 100644 index 0000000000000000000000000000000000000000..d59b0379846648f228d5cc45284a710e3d2d71c3 --- /dev/null +++ b/vendor/regidesk/MANIFEST.in @@ -0,0 +1 @@ +recursive-include regidesk/templates * \ No newline at end of file diff --git a/vendor/regidesk/regidesk/admin.py b/vendor/regidesk/regidesk/admin.py index 8c38f3f3dad51e4585f3984282c2a4bec5349c1e..48567f3340b5d29e5281cdef851e894f4b81a133 100644 --- a/vendor/regidesk/regidesk/admin.py +++ b/vendor/regidesk/regidesk/admin.py @@ -1,3 +1,26 @@ from django.contrib import admin # Register your models here. +from regidesk.models import BoardingPassTemplate, BoardingPass, CheckIn + +admin.site.register( + BoardingPassTemplate, + list_display=['label','from_address','subject'] +) + +admin.site.register( BoardingPass, + list_display=['to_address','created','sent'], + search_fields=['to_address'], + filter_fields=['created','sent'], + readonly_fields=['created','sent', + 'template', 'to_address', 'from_address', + 'subject', 'body','html_body' ] +) + +admin.site.register( + CheckIn, + list_display=['user','seen','checked_in','checkin_code'], + search_fields=['user','checkin_code'], + filter_fields=['seen','checked_in'], + readonly_fields=['user','seen','checked_in','checkin_code'] +) diff --git a/vendor/regidesk/regidesk/migrations/0001_initial.py b/vendor/regidesk/regidesk/migrations/0001_initial.py new file mode 100644 index 0000000000000000000000000000000000000000..4e66e7210bf284eec8c81fb667ccf6018c272a4e --- /dev/null +++ b/vendor/regidesk/regidesk/migrations/0001_initial.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.8 on 2018-01-06 00:19 +from __future__ import unicode_literals + +import datetime + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + +def create_lca2018_template(apps, schema_editor): + + BoardingPassTemplate = apps.get_model("regidesk", "BoardingPassTemplate") + + body = ("This is the plain text version of your boarding pass for " + "linux.conf.au 2018.\r\n\r\nWhen you check in at LCA, you'll " + "need to show the QR code you can download from " + "{{ qrcode_url }}, or quote registration code: {{ code }} ") + html = ("\r\n \r\n

This is your boarding " + "pass

\r\n

A copy of the QR Code is required " + "for check in, please bring this email on either your " + "phone or on a print out.

\r\n " + "

\r\n" + "

Backup Code: {{ code }}

\r\n \r\n") + template = BoardingPassTemplate(label="LCA2018", + from_address="team@lca2018.org", + subject="Your boarding pass for LCA2018, " + "{{ user.attendee.attendeeprofilebase.attendeeprofile.name }}", + body=body, + html_body=html) + template.save() + + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='BoardingPass', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created', models.DateTimeField(auto_now_add=True, verbose_name='Created')), + ('sent', models.DateTimeField(null=True, verbose_name='Sent')), + ('to_address', models.EmailField(max_length=254, verbose_name='To address')), + ('from_address', models.EmailField(max_length=254, verbose_name='From address')), + ('subject', models.CharField(max_length=255, verbose_name='Subject')), + ('body', models.TextField(verbose_name='Body')), + ('html_body', models.TextField(null=True, verbose_name='HTML Body')), + ], + options={ + 'permissions': (('view_boarding_pass', 'Can view sent boarding passes'), ('send_boarding_pass', 'Can send boarding passes')), + }, + ), + migrations.CreateModel( + name='BoardingPassTemplate', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.CharField(max_length=100, verbose_name='Label')), + ('from_address', models.EmailField(max_length=254, verbose_name='From address')), + ('subject', models.CharField(max_length=100, verbose_name='Subject')), + ('body', models.TextField(verbose_name='Body')), + ('html_body', models.TextField(null=True, verbose_name='HTML Body')), + ], + options={ + 'verbose_name': 'Boarding Pass template', + 'verbose_name_plural': 'Boarding Pass templates', + }, + ), + migrations.CreateModel( + name='CheckIn', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('seen', models.DateTimeField(blank=True, null=True)), + ('checked_in', models.DateTimeField(blank=True, null=True)), + ('checkin_code', models.CharField(db_index=True, max_length=6, unique=True)), + ('_checkin_code_png', models.TextField(blank=True, max_length=512, null=True)), + ('boardingpass', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='regidesk.BoardingPass')), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'permissions': (('view_checkin_details', "Can view the details of other user's checkins"),), + }, + ), + migrations.AddField( + model_name='boardingpass', + name='template', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='regidesk.BoardingPassTemplate', verbose_name='Template'), + ), + migrations.RunPython( + code=create_lca2018_template, + ), + ] diff --git a/vendor/regidesk/regidesk/migrations/__init__.py b/vendor/regidesk/regidesk/migrations/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/vendor/regidesk/regidesk/models.py b/vendor/regidesk/regidesk/models.py index eed7f826b261232579366910fe8deceec0d88ecb..777e3057397804537215f622248faf0ad506bc12 100644 --- a/vendor/regidesk/regidesk/models.py +++ b/vendor/regidesk/regidesk/models.py @@ -1,4 +1,110 @@ -from __future__ import unicode_literals +# -*- coding: utf-8 -*- +import base64 +from datetime import datetime +from decimal import Decimal +from io import BytesIO + +from django.core.exceptions import ValidationError from django.db import models +from django.db.models import Q, F +from django.db.models import Case, When, Value +from django.db.models import Count +from django.db.models.signals import post_save +from django.contrib.auth.models import User +import pyqrcode + +from symposion import constants +from symposion.text_parser import parse from registrasion.models import commerce +from registrasion.util import generate_access_code as generate_code + + +class BoardingPassTemplate(models.Model): + + label = models.CharField(max_length=100, verbose_name="Label") + from_address = models.EmailField(verbose_name="From address") + subject = models.CharField(max_length=100, verbose_name="Subject") + body = models.TextField(verbose_name="Body") + html_body = models.TextField(verbose_name="HTML Body",null=True) + + class Meta: + verbose_name = ("Boarding Pass template") + verbose_name_plural = ("Boarding Pass templates") + +class BoardingPass(models.Model): + + template = models.ForeignKey(BoardingPassTemplate, null=True, blank=True, + on_delete=models.SET_NULL, verbose_name="Template") + created = models.DateTimeField(auto_now_add=True, verbose_name="Created") + sent = models.DateTimeField(null=True, verbose_name="Sent") + to_address = models.EmailField(verbose_name="To address") + from_address = models.EmailField(verbose_name="From address") + subject = models.CharField(max_length=255, verbose_name="Subject") + body = models.TextField(verbose_name="Body") + html_body = models.TextField(verbose_name="HTML Body", null=True) + + class Meta: + permissions = ( + ("view_boarding_pass", "Can view sent boarding passes"), + ("send_boarding_pass", "Can send boarding passes"), + ) + + def __unicode__(self): + return self.checkin.attendee.attendeeprofilebase.attendeeprofile.name + ' ' + self.timestamp.strftime('%Y-%m-%d %H:%M:%S') + + @property + def email_args(self): + return (self.subject, self.body, self.from_address, self.user.email) + +class CheckIn(models.Model): + + user = models.OneToOneField(User) + boardingpass = models.OneToOneField(BoardingPass, null=True, + blank=True, on_delete=models.SET_NULL) + seen = models.DateTimeField(null=True,blank=True) + checked_in = models.DateTimeField(null=True,blank=True) + checkin_code = models.CharField( + max_length=6, + unique=True, + db_index=True, + ) + _checkin_code_png=models.TextField(max_length=512,null=True,blank=True) + + class Meta: + permissions = ( + ("view_checkin_details", "Can view the details of other user's checkins"), + ) + + def save(self, *a, **k): + while not self.checkin_code: + checkin_code = generate_code() + if CheckIn.objects.filter(checkin_code=checkin_code).count() == 0: + self.checkin_code = checkin_code + return super(CheckIn, self).save(*a, **k) + + @property + def code(self): + return self.checkin_code + + @property + def qrcode(self): + """Returns the QR Code for this checkin's code. + + If this is the first time the QR code has been generated, cache it on the object. + If a code has already been cached, serve that. + + Returns the raw PNG blob, unless b64=True, in which case the return value + is the base64encoded PNG blob.""" + + if not self.code: + return None + if not self._checkin_code_png: + qrcode = pyqrcode.create(self.code) + qr_io = BytesIO() + qrcode.png(qr_io, scale=6) + qr_io.seek(0) + self._checkin_code_png = base64.b64encode(qr_io.read()).decode('UTF-8') + self.save() + + return self._checkin_code_png diff --git a/vendor/regidesk/regidesk/templates/regidesk/_bp_prepare_help.html b/vendor/regidesk/regidesk/templates/regidesk/_bp_prepare_help.html new file mode 100644 index 0000000000000000000000000000000000000000..912c61db1aff425089aa24ff5dec342a1b1f0c39 --- /dev/null +++ b/vendor/regidesk/regidesk/templates/regidesk/_bp_prepare_help.html @@ -0,0 +1,9 @@ + Body may include the following variables which will be substituted in the email with a value + specific to each proposal: + diff --git a/vendor/regidesk/regidesk/templates/regidesk/base.html b/vendor/regidesk/regidesk/templates/regidesk/base.html new file mode 100644 index 0000000000000000000000000000000000000000..547bdc2cfeed55b23d3a969873c8a2f0fa0f9db3 --- /dev/null +++ b/vendor/regidesk/regidesk/templates/regidesk/base.html @@ -0,0 +1,46 @@ +{% extends "site_base.html" %} +{% load staticfiles %} + +{% load i18n %} + +{% block body_class %}reviews{% endblock %} + +{% block body_outer %} +
+
+
+ +
+
+ {% block body %} + {% endblock %} +
+
+
+{% endblock %} +{% block extra_script %} + + + + + +{% endblock %} diff --git a/vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html b/vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html new file mode 100644 index 0000000000000000000000000000000000000000..99314765b7fdbb85d87a7035fa2ee258d11473d4 --- /dev/null +++ b/vendor/regidesk/regidesk/templates/regidesk/boardingpass_overview.html @@ -0,0 +1,179 @@ +{% extends "regidesk/base.html" %} + +{% load i18n %} +{% load registrasion_tags %} +{% load lca2018_tags %} +{% items_purchased as purchased %} +{% items_pending as pending %} +{% items_purchased 1 as ticket %} +{% total_items_purchased 2 as penguin_dinner_count %} +{% total_items_purchased 3 as speakers_dinner_count %} +{% total_items_purchased 4 as pdns_count %} +{% ticket_type as ticket_type %} + +{% block body_class %}{{ block.super }} review-results{% endblock %} + +{% block extra_style %} +{{ block.super }} + +{% endblock %} + +{% block body %} + +

Boarding Pass Overview

+ +
+ + {% csrf_token %} +

+ Select one or more attendees (0 currently selected) +
+ then pick an email template + +
+ +

+ + + + + + + + + + + + + + {% for attendee in attendees %} + + + + + + + + + + {% endfor %} + +
#Attendee NameTicket TypeAttendee emailCheckin CodeNotified?
{{ attendee.id }}{{ attendee.attendeeprofilebase.attendeeprofile.name }}{{ attendee.ticket_type }}{{ attendee.user.email }}{{ attendee.user.checkin.code }} + {% if attendee.user.checkin %} + {% if attendee.user.checkin.boardingpass %} + Boarding pass sent
+ {% else %} + Checkin Created + {% endif %} + {% else %} + Pending + {% endif %} +
+
+{% endblock %} + +{% block extra_script %} + + + + + +{% endblock %} diff --git a/vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html b/vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html new file mode 100644 index 0000000000000000000000000000000000000000..903b9c0ffc3bc1d2655060cafcfcada48c087f99 --- /dev/null +++ b/vendor/regidesk/regidesk/templates/regidesk/boardingpass_prepare.html @@ -0,0 +1,74 @@ +{% extends "regidesk/base.html" %} + +{% load i18n %} +{% load registrasion_tags %} +{% load lca2018_tags %} +{% items_purchased as purchased %} +{% items_pending as pending %} +{% items_purchased 1 as ticket %} +{% total_items_purchased 2 as penguin_dinner_count %} +{% total_items_purchased 3 as speakers_dinner_count %} +{% total_items_purchased 4 as pdns_count %} +{% ticket_type as ticket_type %} + + +{% block body %} +

BoardingPass Preparation

+ +
+
+

Attendees

+ + {% for attendee in attendees %} + {% with profile=attendee.attendeeprofilebase.attendeeprofile %} + + + + {% endwith %} + {% endfor %} +
+ {{ profile.name }} ({{ attendee.user.email }})
+ {{ attendee.ticket_type }}
+ {{ profile.company }}
+ {{ profile.free_text_1 }}
+ {{ profile.free_text_2 }}
+
+
+
+

Email

+ +
+ + {% csrf_token %} + + + +
+ + +
+ + + + +
+ +
+
{{ rendered_template.plain }}
+
{{ rendered_template.html }}
+
+
+ + + + {% include "regidesk/_bp_prepare_help.html" %} + + + Cancel +
+
+ +{% endblock %} diff --git a/vendor/regidesk/regidesk/urls.py b/vendor/regidesk/regidesk/urls.py index 9f85a14682ab09b7de593a051c1bf8dd334cbd34..4268ae6533bf951a362ed7374f577ea0455d9d5e 100644 --- a/vendor/regidesk/regidesk/urls.py +++ b/vendor/regidesk/regidesk/urls.py @@ -2,5 +2,11 @@ from django.conf.urls import url from regidesk import views +app_name='regidesk' urlpatterns = [ + url(r"^([A-Z0-9]{6}$)", views.boarding_overview, name="checkin_detail"), + url(r"^([A-Z0-9]{6}).png$", views.checkin_png, name="checkin_png"), + url(r"^overview/([a-z]+)?$", views.boarding_overview, name="boarding_overview"), + url(r"^prepare_passes/", views.boarding_prepare, name="boarding_prepare"), + url(r"^send_passes/", views.boarding_send, name="boarding_send") ] diff --git a/vendor/regidesk/regidesk/views.py b/vendor/regidesk/regidesk/views.py index c9a594e2d8b34e63a02c522d433e51c39ef8859a..e37ee95d07f762fa4def060e01a564645cc2c7b1 100644 --- a/vendor/regidesk/regidesk/views.py +++ b/vendor/regidesk/regidesk/views.py @@ -1,20 +1,207 @@ -from regidesk import forms -from regidesk import models +import base64 +import logging +from datetime import datetime from django.core.exceptions import ValidationError +from django.core.mail import EmailMultiAlternatives from django.conf import settings from django.contrib import messages -from django.contrib.auth.decorators import user_passes_test +from django.contrib.auth.decorators import permission_required, user_passes_test, login_required from django.db import transaction +from django.db.models import F, Q +from django.db.models import Count, Max, Sum from django.http import Http404 -from django.http import HttpResponse +from django.http import HttpResponse, HttpResponseBadRequest from django.shortcuts import redirect, render +from django.template import Template, Context +from django.urls import reverse -from registrasion.models import commerce - +from registrasion import util +from registrasion.models import commerce, people from symposion.conference.models import Conference +from regidesk import forms +from regidesk.models import BoardingPass, BoardingPassTemplate, CheckIn + + + +AttendeeProfile = util.get_object_from_name(settings.ATTENDEE_PROFILE_MODEL) + def _staff_only(user): ''' Returns true if the user is staff. ''' return user.is_staff +@permission_required("regidesk.view_boarding_pass") +def boarding_overview(request, boarding_state="pending"): + + tickets = commerce.LineItem.objects.select_related( + "invoice","invoice__user__attendee","product__category" + ).filter( + invoice__status=commerce.Invoice.STATUS_PAID, + product__category=settings.TICKET_PRODUCT_CATEGORY, + price__gte=0 + ) + + ticketholders = { ticket.invoice.user: ticket.product.name for ticket in tickets } + + attendees = people.Attendee.objects.select_related( + "attendeeprofilebase", + "attendeeprofilebase__attendeeprofile", + "user", + "user__checkin" + ).filter(user__in=ticketholders) + + profiles = AttendeeProfile.objects.filter( + attendee__in=attendees + ).select_related( + "attendee", "attendee__user", + ) + profiles_by_attendee = dict((i.attendee, i) for i in profiles) + + bp_templates = BoardingPassTemplate.objects.all() + + ctx = { + "boarding_state": boarding_state, + "attendees": attendees, + "profiles": profiles_by_attendee, + "templates": bp_templates, + } + + return render(request, "regidesk/boardingpass_overview.html", ctx) + +@login_required +def checkin_png(request, checkin_code): + + checkin = CheckIn.objects.get(checkin_code=checkin_code) + if not checkin: + raise Http404() + + if not request.user.has_perm("regidesk.view_checkin_details"): + if request.user != checkin.user: + raise Http404() + + response = HttpResponse() + response["Content-Type"] = "image/png" + response["Content-Disposition"] = 'inline; filename="qrcode.png"' + + qrcode = base64.b64decode(checkin.qrcode) + response.write(qrcode) + + return response + +@permission_required("regidesk.send_boarding_pass") +def boarding_prepare(request): + + attendee_pks = [] + try: + for pk in request.POST.getlist("_selected_action"): + attendee_pks.append(int(pk)) + except ValueError: + return HttpResponseBadRequest() + attendees = people.Attendee.objects.filter(pk__in=attendee_pks) + attendees = attendees.select_related( + "user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile") + + sample_checkin = CheckIn.objects.get_or_create(user=attendees[0].user)[0] + rendered_template = {} + sample_ctx = {} + + bp_template_pk = request.POST.get("template", "") + if bp_template_pk: + bp_template = BoardingPassTemplate.objects.get(pk=bp_template_pk) + + sample_ctx = { + "user": sample_checkin.user, + "boardingpass": sample_checkin.boardingpass, + "code": sample_checkin.code, + "qrcode": sample_checkin.qrcode, + "qrcode_url": request.build_absolute_uri( + reverse("regidesk:checkin_png", args=[sample_checkin.code])), + } + ctx = Context(sample_ctx) + subject = Template(bp_template.subject).render(ctx) + rendered_template['plain'] = Template(bp_template.body).render(ctx) + rendered_template['html'] = Template(bp_template.html_body).render(ctx) + else: + bp_template = None + subject = None + + ctx = { + "attendees": attendees, + "template": bp_template, + "attendee_pks": attendee_pks, + "rendered_template": rendered_template, + "subject": subject, + "sample": sample_ctx, + } + + request.session.set_expiry=(300) + request.session['boarding_attendees'] = attendee_pks + request.session['template'] = bp_template.pk + response = render(request, "regidesk/boardingpass_prepare.html", ctx) + + return response + +@permission_required("regidesk.send_boarding_pass") +def boarding_send(request): + + attendees = people.Attendee.objects.filter(pk__in=request.session['boarding_attendees']) + attendees = attendees.select_related( + "user", "attendeeprofilebase", "attendeeprofilebase__attendeeprofile") + + logging.debug(attendees) + + template_pk = request.session['template'] + template = BoardingPassTemplate.objects.get(pk=template_pk) + + for attendee in attendees: + + user = attendee.user + checkin = CheckIn.objects.get_or_create(user=user) + ctx = { + "user": user, + "checkin": user.checkin, + "code": user.checkin.code, + "qrcode": user.checkin.qrcode, + "qrcode_url": request.build_absolute_uri( + reverse("regidesk:checkin_png", args=[user.checkin.code])), + } + ctx = Context(ctx) + + subject = Template(template.subject).render(ctx) + body = Template(template.body).render(ctx) + if template.html_body: + html_body = Template(template.html_body).render(ctx) + else: + html_body = None + + bpass = BoardingPass(template=template, to_address=user.email, + from_address=template.from_address, + subject=subject, body=body, + html_body=html_body + ) + bpass.save() + if user.checkin.boardingpass: + user.checkin.boardingpass.delete() + user.checkin.boardingpass = bpass + user.checkin.save() + + with transaction.atomic(): + + msg = EmailMultiAlternatives( + bpass.subject, + bpass.body, + bpass.from_address, + [bpass.to_address,], + ) + if bpass.html_body: + msg.attach_alternative(bpass.html_body, "text/html") + + msg.send() + + bpass.sent = datetime.now() + bpass.save() + messages.success(request, "Sent boarding pass to %s" % attendee) + request.session['boarding_attendees'].remove(attendee.pk) + + return redirect("regidesk:boarding_overview") diff --git a/vendor/regidesk/requirements.txt b/vendor/regidesk/requirements.txt index f42772e96901f972d1bf482f8f33f8622ee14a34..fb4e31e0d7e19a90656569216e2fff857f9eb6ee 100644 --- a/vendor/regidesk/requirements.txt +++ b/vendor/regidesk/requirements.txt @@ -1,3 +1,4 @@ django-countries>=4.0 requests>=2.11.1 - +pypng +pyqrcode diff --git a/vendor/regidesk/setup.py b/vendor/regidesk/setup.py index 461841fb06acb5410c6c1ce51032a8816a6438ce..3440ab3855a357a8dea3ace636075e12c8bddd2d 100644 --- a/vendor/regidesk/setup.py +++ b/vendor/regidesk/setup.py @@ -18,7 +18,7 @@ setup( name="registrasion-desk", author="James Polley", author_email="jamezpolley@gmail", - version=registripe.__version__, + version=regidesk.__version__, description="Registration desk functionality for registrasion", url="http://gitlab.com/lca2018/registrasion-desk/", packages=find_packages(), diff --git a/vendored_requirements.txt b/vendored_requirements.txt index 8b01002b13810c706e484125483f5c351f9808bb..950fbc9b58094f4cc6429ee4311eef0ffed043b1 100644 --- a/vendored_requirements.txt +++ b/vendored_requirements.txt @@ -1,2 +1,3 @@ vendor/registrasion vendor/registripe +vendor/regidesk