Skip to content

Commit

Permalink
ESNcards Applications Plugin (#26)
Browse files Browse the repository at this point in the history
* Created plugin & added forms rendering WIP

* WIP tailwind forms

* Added HTMX, flowbite, added ESNcard application form & process

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* Added breadcrumbs with view decorators

* [pre-commit.ci] auto fixes from pre-commit.com hooks

* Added public page, reformated by black

* Added footer, added logos

* [pre-commit.ci] auto fixes from pre-commit.com hooks

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
thejoeejoee and pre-commit-ci[bot] committed Feb 8, 2022
1 parent 5893762 commit 73693d3
Show file tree
Hide file tree
Showing 64 changed files with 1,424 additions and 79 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ DA_CMD = $(cmd)
ARG =

MODELS_PNG = models.png
GRAPH_MODELS_CMD = graph_models accounts plugins auth sections universities \
GRAPH_MODELS_CMD = graph_models accounts plugins auth sections universities esncards \
--verbose-names --disable-sort-fields \
--pydot -X 'ContentType|Base*Model' \
-g -o $(MODELS_PNG)
Expand Down Expand Up @@ -50,7 +50,7 @@ upbd: ## Build and runs all needed docker containers in detached mode
docker-compose up --build --detach

upd: ## Runs all needed docker containers in detached mode
docker-compose up --build --detach
docker-compose up --detach

up: ## Runs all needed docker containers
docker-compose up
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ services:
depends_on:
- web
environment:
- VIRTUAL_HOST=fiesta.localhost
- VIRTUAL_HOST=*.fiesta.localhost
- VIRTUAL_PORT=80

localproxy:
Expand Down
25 changes: 17 additions & 8 deletions fiesta/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
FROM python:3.9.7-alpine as builder

RUN apk add --no-cache --virtual build-deps \
build-base gcc python3-dev musl-dev gettext-dev libffi-dev g++ postgresql-dev mariadb-dev \
musl-dev rust cargo patchelf

COPY pyproject.toml poetry.lock ./

RUN python -m pip install poetry && \
poetry config virtualenvs.create false && \
poetry install && \
apk del build-deps


# pull official base image
FROM python:3.9.7-alpine

COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages

# set work directory
WORKDIR /usr/src/app

Expand All @@ -16,16 +32,9 @@ COPY pyproject.toml poetry.lock /usr/src/app/
RUN addgroup -S 1000 \
&& adduser -s /bin/sh -D -S -G 1000 1000 \
&& apk update \
# build deps
&& apk add --virtual build-deps build-base gcc python3-dev musl-dev gettext-dev libffi-dev \
# runtime deps
&& apk add postgresql-dev gettext tzdata bash graphviz graphviz-dev ttf-freefont mariadb-dev \
&& apk add gettext tzdata bash graphviz graphviz-dev ttf-freefont postgresql-dev mariadb-dev \
# && pipenv install psycopg2-binary --skip-lock --dev \
&& pip install poetry \
&& poetry config virtualenvs.create false \
&& poetry install \
&& apk del build-deps \
&& rm -rf /var/cache/apk/* \
&& mkdir -p /usr/src/static /usr/src/media /usr/src/app \
&& ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

Expand Down
4 changes: 4 additions & 0 deletions fiesta/apps/accounts/apps.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from django.utils.translation import gettext_lazy as _

from apps.plugins.plugin import PluginAppConfig


Expand All @@ -7,5 +9,7 @@ class AccountsConfig(PluginAppConfig):

configuration_model = "accounts.AccountsConfiguration"

title = _("Users")


__all__ = ["AccountsConfig"]
49 changes: 49 additions & 0 deletions fiesta/apps/accounts/templates/accounts/auth/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{% extends "fiesta/base.html" %}

{% block body %}
<div class="flex justify-center items-center mt-6">
<div class="relative px-4 w-full max-w-md h-full md:h-auto">
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<div class="flex justify-end p-2">
<button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-800 dark:hover:text-white" data-modal-toggle="authentication-modal">
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
</button>
</div>
<form class="px-6 pb-4 space-y-6 lg:px-8 sm:pb-6 xl:pb-8" action="#">
<h3 class="text-xl font-medium text-gray-900 dark:text-white">Sign in to our platform</h3>
<div>
<label for="email" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">Your
email</label>
<input type="email" name="email" id="email" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" placeholder="[email protected]" required>
</div>
<div>
<label for="password" class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300">Your
password</label>
<input type="password" name="password" id="password" placeholder="••••••••" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white" required>
</div>
<div class="flex justify-between">
<div class="flex items-start">
<div class="flex items-center h-5">
<input id="remember" aria-describedby="remember" type="checkbox" class="w-4 h-4 bg-gray-50 rounded border border-gray-300 focus:ring-3 focus:ring-blue-300 dark:bg-gray-600 dark:border-gray-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800" required>
</div>
<div class="ml-3 text-sm">
<label for="remember" class="font-medium text-gray-900 dark:text-gray-300">Remember
me</label>
</div>
</div>
<a href="#" class="text-sm text-blue-700 hover:underline dark:text-blue-500">Lost Password?</a>
</div>
<button type="submit" class="w-full text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
Login to your account
</button>
<div class="text-sm font-medium text-gray-500 dark:text-gray-300">
Not registered? <a href="#" class="text-blue-700 hover:underline dark:text-blue-500">Create
account</a>
</div>
</form>
</div>
</div>
</div>
{% endblock body %}
2 changes: 1 addition & 1 deletion fiesta/apps/accounts/templates/accounts/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "fiesta/base.html" %}

{% block main %}

<h1>Users</h1>
{% endblock %}
45 changes: 45 additions & 0 deletions fiesta/apps/accounts/templates/fiesta/parts/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{% load static %}
<footer>
<div class="bg-gray-100">
<div class="container px-5 py-6 mx-auto flex items-center sm:flex-row flex-col">
<a class="flex title-font font-medium items-center md:justify-start justify-center text-gray-900">
<svg class="mr-3 h-10 w-10" viewBox="0 0 212 212">
<use href="{% static "panda.svg" %}#panda"></use>
</svg>
</a>
<p class="text-gray-500 sm:ml-6 sm:mt-0 mt-4">
© {% now 'Y' %} ESN VUT Brno —
<a href="https://github.com/esnvutbrno"
rel="noopener noreferrer"
class="text-gray-600 ml-1"
target="_blank">
@esnvutbrno
</a>
</p>
<span class="inline-flex sm:ml-auto sm:mt-0 mt-4 justify-center sm:justify-start">
<a class="text-gray-500">
<svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24">
<path d="M18 2h-3a5 5 0 00-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 011-1h3z"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24">
<path d="M23 3a10.9 10.9 0 01-3.14 1.53 4.48 4.48 0 00-7.86 3v1A10.66 10.66 0 013 4s-4 9 5 13a11.64 11.64 0 01-7 2c9 5 20 0 20-11.5a4.5 4.5 0 00-.08-.83A7.72 7.72 0 0023 3z"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" class="w-5 h-5" viewBox="0 0 24 24">
<rect width="20" height="20" x="2" y="2" rx="5" ry="5"></rect>
<path d="M16 11.37A4 4 0 1112.63 8 4 4 0 0116 11.37zm1.5-4.87h.01"></path>
</svg>
</a>
<a class="ml-3 text-gray-500">
<svg fill="currentColor" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="0" class="w-5 h-5" viewBox="0 0 24 24">
<path stroke="none" d="M16 8a6 6 0 016 6v7h-4v-7a2 2 0 00-2-2 2 2 0 00-2 2v7h-4v-7a6 6 0 016-6zM2 9h4v12H2z"></path>
<circle cx="4" cy="4" r="2" stroke="none"></circle>
</svg>
</a>
</span>
</div>
</div>
</footer>
20 changes: 6 additions & 14 deletions fiesta/apps/accounts/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,11 @@
# Define your urls here
from django.views.generic import TemplateView


class MyView(TemplateView):
template_name = "accounts/main.html"

def get_context_data(self, **kwargs):
data = super().get_context_data(**kwargs)

self.request.plugin.app_config.urls[0]

return data


urlpatterns = [
path("", TemplateView.as_view(template_name="accounts/main.html"), name="index"),
path("test", MyView.as_view(), name="view"),
path(
"auth/login",
TemplateView.as_view(template_name="accounts/auth/login.html"),
name="login",
),
path("", TemplateView.as_view(template_name="accounts/index.html")),
]
Empty file.
18 changes: 18 additions & 0 deletions fiesta/apps/esncards/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.contrib import admin
from polymorphic.admin import PolymorphicChildModelAdmin

from .models import ESNcardApplication, ESNcardsConfiguration
from apps.plugins.models import BasePluginConfiguration


@admin.register(ESNcardsConfiguration)
class EsncardsConfigurationAdmin(PolymorphicChildModelAdmin):
base_model = BasePluginConfiguration
show_in_index = True


@admin.register(ESNcardApplication)
class ESNcardApplicationAdmin(admin.ModelAdmin):
list_display = ["user", "section", "state", "created"]
list_filter = ["section", "state"]
raw_id_fields = ["user", "section"]
15 changes: 15 additions & 0 deletions fiesta/apps/esncards/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from django.utils.translation import gettext_lazy as _

from apps.plugins.plugin import PluginAppConfig


class ESNcardsConfig(PluginAppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.esncards"

configuration_model = "esncards.ESNcardsConfiguration"

title = _("ESNcards")


__all__ = ["ESNcardsConfig"]
Empty file.
48 changes: 48 additions & 0 deletions fiesta/apps/esncards/forms/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from django.forms import CharField, HiddenInput, ImageField
from django.utils.translation import gettext_lazy as _

from apps.esncards.models import ESNcardApplication
from apps.fiestaforms.forms import BaseModelForm, DateInput


class ESNcardApplicationForm(BaseModelForm):
title = _("Application Form")

photo = ImageField(
label=_("Holder photo"),
required=False,
help_text=_("Front passport-sized photo is needed."),
)

university_name = CharField(label=_("Studies at"), disabled=True)
section_name = CharField(label=_("ESN section"), disabled=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

# self.fields['first_name'].disabled = True
# self.fields['last_name'].disabled = True
# self.fields['nationality'].disabled = True
self.fields["section"].disabled = True
self.fields["university"].disabled = True

self.initial["section_name"] = self.initial["section"].name
self.initial["university_name"] = self.initial["university"].name

class Meta:
model = ESNcardApplication
fields = (
"first_name",
"last_name",
"nationality",
"birth_date",
"user",
"section",
"university",
)
widgets = {
"birth_date": DateInput,
"user": HiddenInput,
"section": HiddenInput,
"university": HiddenInput,
}
Empty file.
7 changes: 7 additions & 0 deletions fiesta/apps/esncards/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .application import ESNcardApplication
from .configuration import ESNcardsConfiguration

__all__ = [
"ESNcardsConfiguration",
"ESNcardApplication",
]
89 changes: 89 additions & 0 deletions fiesta/apps/esncards/models/application.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
from __future__ import annotations

from datetime import datetime
from typing import TypedDict

from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.db.models import TextChoices
from django.utils.translation import gettext_lazy as _
from django_countries.fields import CountryField
from django_lifecycle import BEFORE_CREATE, BEFORE_SAVE, LifecycleModelMixin, hook

from apps.utils.models import BaseTimestampedModel


class ESNcardApplication(LifecycleModelMixin, BaseTimestampedModel):
# copied from user
first_name = models.CharField(
verbose_name=_("first name"),
max_length=150,
)
last_name = models.CharField(verbose_name=_("last name"), max_length=150)
nationality = CountryField(verbose_name=_("nationality"))
university = models.ForeignKey(
"universities.University",
on_delete=models.RESTRICT,
verbose_name=_("university"),
)

# filled by user
birth_date = models.DateField(verbose_name=_("birth date"))

# related to request
section = models.ForeignKey(
"sections.Section",
on_delete=models.RESTRICT,
verbose_name=_("section"),
)
user = models.ForeignKey(
"accounts.User",
on_delete=models.RESTRICT,
verbose_name=_("issuer"),
)

class State(TextChoices):
CREATED = "created", _("Created")
ACCEPTED = "accepted", _("Accepted")
READY_TO_PICKUP = "ready", _("Ready to pickup")
ISSUED = "issued", _("Issued")

DECLINED = "declined", _("Declined")
CANCELLED = "cancelled", _("Cancelled")

state = models.TextField(
max_length=16,
choices=State.choices,
default=State.CREATED,
)

history: list["HistoryRecord"] = models.JSONField(
default=list,
encoder=DjangoJSONEncoder,
)

class HistoryRecord(TypedDict):
timestamp: datetime
initial_state: ESNcardApplication.State | str
final_state: ESNcardApplication.State | str

class Meta:
verbose_name = _("ESNcard application")
verbose_name_plural = _("ESNcard applications")

@hook(BEFORE_SAVE, when="state", has_changed=True)
@hook(BEFORE_CREATE)
def on_state_change(self):
self.history.append(
self.HistoryRecord(
timestamp=datetime.now(),
initial_state=self.initial_value("state"),
final_state=self.state,
)
)

def __str__(self):
return _("ESNcard Application: {}").format(self.get_state_display())


__all__ = ["ESNcardApplication"]
Loading

0 comments on commit 73693d3

Please sign in to comment.