Add Venueless integration to Regidesk
Create Venueless login token as part of checkin to allow attendee to join an online event from the dashboard.
This commit is contained in:
parent
ef148ea482
commit
7c77001d5e
11 changed files with 142 additions and 18 deletions
|
@ -570,3 +570,10 @@ SPEAKERS_DINNER_ADULT = SpeakersDinnerCat.create(
|
||||||
# "Infant must be seated in an adult's lap. "
|
# "Infant must be seated in an adult's lap. "
|
||||||
# "No food or beverage service.",
|
# "No food or beverage service.",
|
||||||
# timedelta(hours=1))
|
# timedelta(hours=1))
|
||||||
|
|
||||||
|
|
||||||
|
# Venueless integration
|
||||||
|
VENUELESS_URL = os.environ.get('VENUELESS_URL', None)
|
||||||
|
VENUELESS_AUDIENCE = os.environ.get('VENUELESS_AUDIENCE', "venueless")
|
||||||
|
VENUELESS_TOKEN_ISSUER = os.environ.get('VENUELESS_TOKEN_ISSUER', "any")
|
||||||
|
VENUELESS_SECRET = os.environ.get('VENUELESS_SECRET', SECRET_KEY)
|
||||||
|
|
|
@ -41,6 +41,18 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% flag "venueless_dashboard" %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12 mb-3">
|
||||||
|
<h3>Join the Conference</h3>
|
||||||
|
<p>The conference is now open. Please join us to watch talks and interact with the other delegates.</p>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-lg btn-primary" role="button" href="{% venueless_login_url %}">Launch Conference</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endflag %}
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-6 mb-3 mb-md-0">
|
<div class="col-md-6 mb-3 mb-md-0">
|
||||||
<h3>Attendee Profile</h3>
|
<h3>Attendee Profile</h3>
|
||||||
|
|
|
@ -8,6 +8,7 @@ from easy_thumbnails.files import get_thumbnailer
|
||||||
from registrasion.templatetags import registrasion_tags
|
from registrasion.templatetags import registrasion_tags
|
||||||
from symposion.conference import models as conference_models
|
from symposion.conference import models as conference_models
|
||||||
from symposion.schedule.models import Track
|
from symposion.schedule.models import Track
|
||||||
|
from regidesk.models import CheckIn
|
||||||
|
|
||||||
CONFERENCE_ID = settings.CONFERENCE_ID
|
CONFERENCE_ID = settings.CONFERENCE_ID
|
||||||
GST_RATE = settings.GST_RATE
|
GST_RATE = settings.GST_RATE
|
||||||
|
@ -158,3 +159,10 @@ def ticket_type(context):
|
||||||
|
|
||||||
# Default to product type
|
# Default to product type
|
||||||
return ticket_type
|
return ticket_type
|
||||||
|
|
||||||
|
|
||||||
|
@register.simple_tag(takes_context=True)
|
||||||
|
def venueless_login_url(context):
|
||||||
|
user=context.request.user
|
||||||
|
checkin = CheckIn.objects.get_or_create(user=user)[0]
|
||||||
|
return f'{settings.VENUELESS_URL}/#token={checkin.venueless_token}'
|
||||||
|
|
|
@ -36,6 +36,7 @@ django-ical==1.7.1
|
||||||
# Registrasion reqs
|
# Registrasion reqs
|
||||||
django-nested-admin==3.3.2
|
django-nested-admin==3.3.2
|
||||||
CairoSVG==2.4.2
|
CairoSVG==2.4.2
|
||||||
|
PyJWT==2.0.0
|
||||||
|
|
||||||
# Registripe
|
# Registripe
|
||||||
django-countries>=6.1.3
|
django-countries>=6.1.3
|
||||||
|
|
|
@ -14,7 +14,7 @@ def create_lca2018_template(apps, schema_editor):
|
||||||
BoardingPassTemplate = apps.get_model("regidesk", "BoardingPassTemplate")
|
BoardingPassTemplate = apps.get_model("regidesk", "BoardingPassTemplate")
|
||||||
|
|
||||||
body = ("This is the plain text version of your boarding pass for "
|
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 "
|
"linux.conf.au 2021.\r\n\r\nWhen you check in at LCA, you'll "
|
||||||
"need to show the QR code you can download from "
|
"need to show the QR code you can download from "
|
||||||
"{{ qrcode_url }}, or quote registration code: {{ code }} ")
|
"{{ qrcode_url }}, or quote registration code: {{ code }} ")
|
||||||
html = ("<html>\r\n <body>\r\n <p>This is your boarding "
|
html = ("<html>\r\n <body>\r\n <p>This is your boarding "
|
||||||
|
@ -23,9 +23,9 @@ def create_lca2018_template(apps, schema_editor):
|
||||||
"phone or on a print out.</p>\r\n "
|
"phone or on a print out.</p>\r\n "
|
||||||
"<p><img src=\"data:image/png;base64,{{ qrcode }}\" /></p>\r\n"
|
"<p><img src=\"data:image/png;base64,{{ qrcode }}\" /></p>\r\n"
|
||||||
" <p>Backup Code: {{ code }}</p>\r\n </body>\r\n</html>")
|
" <p>Backup Code: {{ code }}</p>\r\n </body>\r\n</html>")
|
||||||
template = BoardingPassTemplate(label="LCA2018",
|
template = BoardingPassTemplate(label="LCA2021",
|
||||||
from_address="team@lca2018.org",
|
from_address="contact@lca2021.linux.org.au",
|
||||||
subject="Your boarding pass for LCA2018, "
|
subject="Your boarding pass for LCA2021, "
|
||||||
"{{ user.attendee.attendeeprofilebase.attendeeprofile.name }}",
|
"{{ user.attendee.attendeeprofilebase.attendeeprofile.name }}",
|
||||||
body=body,
|
body=body,
|
||||||
html_body=html)
|
html_body=html)
|
||||||
|
|
28
vendor/regidesk/regidesk/migrations/0005_auto_20210108_2347.py
vendored
Normal file
28
vendor/regidesk/regidesk/migrations/0005_auto_20210108_2347.py
vendored
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
# Generated by Django 2.2.17 on 2021-01-08 23:47
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('regidesk', '0004_auto_20180121_1140'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='checkin',
|
||||||
|
name='venueless_traits',
|
||||||
|
field=models.TextField(blank=True, max_length=256, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='checkin',
|
||||||
|
name='venueless_user_id',
|
||||||
|
field=models.TextField(blank=True, max_length=40, null=True),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='checkin',
|
||||||
|
name='_venueless_token',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
46
vendor/regidesk/regidesk/models.py
vendored
46
vendor/regidesk/regidesk/models.py
vendored
|
@ -1,12 +1,14 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import base64
|
import base64
|
||||||
from datetime import datetime
|
import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
import jwt
|
||||||
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, F
|
from django.db.models import Q, F
|
||||||
from django.db.models import Case, When, Value
|
from django.db.models import Case, When, Value
|
||||||
|
@ -79,6 +81,10 @@ class CheckIn(models.Model):
|
||||||
needs_review = models.BooleanField(default=False)
|
needs_review = models.BooleanField(default=False)
|
||||||
review_text = models.TextField(blank=True)
|
review_text = models.TextField(blank=True)
|
||||||
|
|
||||||
|
venueless_user_id = models.TextField(max_length=40, null=True, blank=True)
|
||||||
|
venueless_traits = models.TextField(max_length=256, null=True, blank=True)
|
||||||
|
_venueless_token = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
("view_checkin_details", "Can view the details of other user's checkins"),
|
("view_checkin_details", "Can view the details of other user's checkins"),
|
||||||
|
@ -143,3 +149,41 @@ class CheckIn(models.Model):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
return self._checkin_code_png
|
return self._checkin_code_png
|
||||||
|
|
||||||
|
@property
|
||||||
|
def venueless_token(self):
|
||||||
|
"""Returns the Venueless JWT token for this checkin's code."""
|
||||||
|
updated = False
|
||||||
|
if not self.venueless_user_id:
|
||||||
|
self.venueless_user_id = self.checkin_code
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if not self.venueless_traits:
|
||||||
|
self.venueless_traits = ".".join(("attendee",))
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if not self._venueless_token:
|
||||||
|
self._venueless_token = self._generate_venueless_token()
|
||||||
|
updated = True
|
||||||
|
|
||||||
|
if updated:
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
return self._venueless_token
|
||||||
|
|
||||||
|
def _generate_venueless_token(self):
|
||||||
|
""" Generate token for Venueless login """
|
||||||
|
|
||||||
|
issued_at = datetime.datetime.utcnow()
|
||||||
|
expiry = settings.LCA_END + datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"iss": settings.VENUELESS_TOKEN_ISSUER,
|
||||||
|
"aud": settings.VENUELESS_AUDIENCE,
|
||||||
|
"iat": issued_at,
|
||||||
|
"exp": expiry,
|
||||||
|
"uid": self.venueless_user_id,
|
||||||
|
"traits": self.venueless_traits.split(','),
|
||||||
|
}
|
||||||
|
token = jwt.encode(payload, settings.VENUELESS_SECRET, algorithm="HS256")
|
||||||
|
return token
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
{% items_purchased as purchased %}
|
{% items_purchased as purchased %}
|
||||||
{% items_pending as pending %}
|
{% items_pending as pending %}
|
||||||
{% items_purchased 1 as ticket %}
|
{% items_purchased 1 as ticket %}
|
||||||
{% total_items_purchased 2 as penguin_dinner_count %}
|
<!-- {% total_items_purchased 2 as penguin_dinner_count %} -->
|
||||||
{% total_items_purchased 3 as speakers_dinner_count %}
|
<!-- {% total_items_purchased 3 as speakers_dinner_count %} -->
|
||||||
{% total_items_purchased 4 as pdns_count %}
|
<!-- {% total_items_purchased 4 as pdns_count %} -->
|
||||||
{% ticket_type as ticket_type %}
|
{% ticket_type as ticket_type %}
|
||||||
|
|
||||||
{% block header_title %}
|
{% block header_title %}
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
{% items_purchased as purchased %}
|
{% items_purchased as purchased %}
|
||||||
{% items_pending as pending %}
|
{% items_pending as pending %}
|
||||||
{% items_purchased 1 as ticket %}
|
{% items_purchased 1 as ticket %}
|
||||||
{% total_items_purchased 2 as penguin_dinner_count %}
|
<!-- {% total_items_purchased 2 as penguin_dinner_count %} -->
|
||||||
{% total_items_purchased 3 as speakers_dinner_count %}
|
<!-- {% total_items_purchased 3 as speakers_dinner_count %} -->
|
||||||
{% total_items_purchased 4 as pdns_count %}
|
<!-- {% total_items_purchased 4 as pdns_count %} -->
|
||||||
{% ticket_type as ticket_type %}
|
{% ticket_type as ticket_type %}
|
||||||
|
|
||||||
{% block body_class %}{{ block.super }} review-results{% endblock %}
|
{% block body_class %}{{ block.super }} review-results{% endblock %}
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
{% items_purchased as purchased %}
|
{% items_purchased as purchased %}
|
||||||
{% items_pending as pending %}
|
{% items_pending as pending %}
|
||||||
{% items_purchased 1 as ticket %}
|
{% items_purchased 1 as ticket %}
|
||||||
{% total_items_purchased 2 as penguin_dinner_count %}
|
<!-- {% total_items_purchased 2 as penguin_dinner_count %} -->
|
||||||
{% total_items_purchased 3 as speakers_dinner_count %}
|
<!-- {% total_items_purchased 3 as speakers_dinner_count %} -->
|
||||||
{% total_items_purchased 4 as pdns_count %}
|
<!-- {% total_items_purchased 4 as pdns_count %} -->
|
||||||
{% ticket_type as ticket_type %}
|
{% ticket_type as ticket_type %}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@
|
||||||
{% items_purchased as purchased %}
|
{% items_purchased as purchased %}
|
||||||
{% items_pending as pending %}
|
{% items_pending as pending %}
|
||||||
{% items_purchased 1 as ticket %}
|
{% items_purchased 1 as ticket %}
|
||||||
{% items_purchased 6 as shirts %}
|
<!-- {% items_purchased 6 as shirts %} -->
|
||||||
{% total_items_purchased 3 as penguin_dinner_count %}
|
<!-- {% total_items_purchased 3 as penguin_dinner_count %} -->
|
||||||
{% total_items_purchased 4 as speakers_dinner_count %}
|
<!-- {% total_items_purchased 4 as speakers_dinner_count %} -->
|
||||||
{% total_items_purchased 5 as pdns_count %}
|
<!-- {% total_items_purchased 5 as pdns_count %} -->
|
||||||
{% ticket_type as ticket_type %}
|
{% ticket_type as ticket_type %}
|
||||||
|
|
||||||
<a type="button" class="btn btn-outline-primary" href="{% url 'regidesk:check_in_scanner' %}">Return to scanning page</a>
|
<a type="button" class="btn btn-outline-primary" href="{% url 'regidesk:check_in_scanner' %}">Return to scanning page</a>
|
||||||
|
@ -36,6 +36,7 @@
|
||||||
<dt class="col-sm-3">Free Text 2</dt>
|
<dt class="col-sm-3">Free Text 2</dt>
|
||||||
<dd class="col-sm-9">{{ user.attendee.attendeeprofilebase.attendeeprofile.free_text_2 }}</dd>
|
<dd class="col-sm-9">{{ user.attendee.attendeeprofilebase.attendeeprofile.free_text_2 }}</dd>
|
||||||
|
|
||||||
|
{% comment "Not needed for LCA2021 online" %}
|
||||||
<dt class="col-sm-3">Penguin Dinner Tickets</dt>
|
<dt class="col-sm-3">Penguin Dinner Tickets</dt>
|
||||||
<dd class="col-sm-9">{{ penguin_dinner_count }}</dd>
|
<dd class="col-sm-9">{{ penguin_dinner_count }}</dd>
|
||||||
|
|
||||||
|
@ -47,11 +48,13 @@
|
||||||
|
|
||||||
<dt class="col-sm-3">Over 18 years</dt>
|
<dt class="col-sm-3">Over 18 years</dt>
|
||||||
<dd class="col-sm-9">{% if user.attendee.attendeeprofilebase.attendeeprofile.of_legal_age %}Yes{% else %}<strong class="red">NO</strong>{% endif %}</dd>
|
<dd class="col-sm-9">{% if user.attendee.attendeeprofilebase.attendeeprofile.of_legal_age %}Yes{% else %}<strong class="red">NO</strong>{% endif %}</dd>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
<dt class="col-sm-3">Username</dt>
|
<dt class="col-sm-3">Username</dt>
|
||||||
<dd class="col-sm-9">{{ user.username }}</dd>
|
<dd class="col-sm-9">{{ user.username }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
|
{% comment "Not needed for LCA2021 online" %}
|
||||||
<h4>Shirts ordered</h4>
|
<h4>Shirts ordered</h4>
|
||||||
<table class="table card-text">
|
<table class="table card-text">
|
||||||
{% for shirt in shirts%}
|
{% for shirt in shirts%}
|
||||||
|
@ -61,6 +64,23 @@
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
{% endcomment %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card my-3">
|
||||||
|
<div class="card-header">Venueless</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="card-text row">
|
||||||
|
<dt class="col-sm-3">User Id</dt>
|
||||||
|
<dd class="col-sm-9">{{ check_in.venueless_user_id }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3">Traits</dt>
|
||||||
|
<dd class="col-sm-9">{{ check_in.venueless_traits }}</dd>
|
||||||
|
|
||||||
|
<dt class="col-sm-3">Token</dt>
|
||||||
|
<dd class="col-sm-9">{{ check_in.venueless_token|truncatechars:20 }}</dd>
|
||||||
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -108,6 +128,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% comment "Not needed for LCA2021 online" %}
|
||||||
<div class="card {% if check_in.schwag_given %}border-success{% else %}border-danger{% endif %} my-3">
|
<div class="card {% if check_in.schwag_given %}border-success{% else %}border-danger{% endif %} my-3">
|
||||||
<div class="card-header {% if check_in.schwag_given %}text-white bg-success{% endif %}">Schwag</div>
|
<div class="card-header {% if check_in.schwag_given %}text-white bg-success{% endif %}">Schwag</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -121,6 +142,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
<div class="card my-3">
|
<div class="card my-3">
|
||||||
<div class="card-header">Log Exception</div>
|
<div class="card-header">Log Exception</div>
|
||||||
|
@ -133,6 +155,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% comment "Not needed for LCA2021 online" %}
|
||||||
<div class="card my-3 {% if check_in.checked_in_bool and check_in.schwag_given %}border-success{% elif check_in.checked_in_bool or check_in.schwag_given %}card-warning{% else %}card-danger{% endif %}">
|
<div class="card my-3 {% if check_in.checked_in_bool and check_in.schwag_given %}border-success{% elif check_in.checked_in_bool or check_in.schwag_given %}card-warning{% else %}card-danger{% endif %}">
|
||||||
<div class="card-header {% if check_in.checked_in_bool and check_in.schwag_given %}text-white bg-success{% elif check_in.checked_in_bool or check_in.schwag_given %}bg-warning{% endif %}">Bulk actions</div>
|
<div class="card-header {% if check_in.checked_in_bool and check_in.schwag_given %}text-white bg-success{% elif check_in.checked_in_bool or check_in.schwag_given %}bg-warning{% endif %}">Bulk actions</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
@ -153,6 +176,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
<a type="button" class="btn btn-outline-primary" href="{% url 'regidesk:check_in_scanner' %}">Return to scanning page</a>
|
<a type="button" class="btn btn-outline-primary" href="{% url 'regidesk:check_in_scanner' %}">Return to scanning page</a>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue