diff --git a/.github/workflows/workflow.yml b/.github/workflows/audit.yml similarity index 74% rename from .github/workflows/workflow.yml rename to .github/workflows/audit.yml index 2311a2cd..d36cc5ca 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/audit.yml @@ -8,9 +8,17 @@ # # The `django-test-action` action is used to run the tests and requires a # requirements file and a settings directory path. +# +# The `pypa/gh-action-pip-audit` action is used to audit the requirements file +# for security vulnerabilities. It requires a requirements file and can be +# configured to require hashes for all packages. +# +# See: +# - https://github.com/pypa/gh-action-pip-audit +# - https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions -name: Django CI (actions) +name: Audit on: push: branches: [main] @@ -46,12 +54,9 @@ jobs: # run: | # pip install --upgrade pip - - name: Django Test CI - uses: UKnowWhoIm/django-test-action@v0.6.1 - with: - requirements-file: afb/requirements.txt - settings-dir-path: afb/afb - parallel-tests: false - - name: gh-action-pip-audit uses: pypa/gh-action-pip-audit@v1.0.8 + with: + inputs: afb/requirements.txt + summary: true + require-hashes: false diff --git a/.gitignore b/.gitignore index b3dd0852..f62c1749 100644 --- a/.gitignore +++ b/.gitignore @@ -84,5 +84,5 @@ local_settings.py .env* !.env.empty db.sqlite3 -*.txt .vscode/ +tmp/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..ff4a7366 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,26 @@ +# +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +# +# To use pre-commit, install it using pip: +# +# pip install pre-commit +# +# Then, run it on all the files in this repo: +# +# pre-commit run --all-files +# +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +# TODO: Enable black after formatting is standardized. +# - repo: https://github.com/psf/black +# rev: 22.10.0 +# hooks: +# - id: black diff --git a/.ruff.toml b/.ruff.toml index a5b363bd..337ceed3 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -1,6 +1,6 @@ project_type = "python" -#python_version = "3.8" +python_version = "3.10" linter = "flake8" formatter = "black" test_framework = "pytest" diff --git a/README.md b/README.md index fce90c11..b1eb74f8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,14 @@ # afb-requests -- 2023-09-21 + +## Commands + + +```bash + $ pip-compile --output-file=- requirements.in > requirements.txt + + $ pip-compile --upgrade --output-file=- requirements.in | tee requirements.txt + + $ pip-compile --output-file=- > requirements.txt + +``` diff --git a/afb/afb/settings.py b/afb/afb/settings.py index 0da60b1f..37c86d45 100644 --- a/afb/afb/settings.py +++ b/afb/afb/settings.py @@ -40,6 +40,7 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'phonenumber_field', 'crispy_forms', 'tailwind', "crispy_tailwind", @@ -54,6 +55,12 @@ CRISPY_TEMPLATE_PACK = "tailwind" +PHONENUMBER_DB_FORMAT = "INTERNATIONAL" +PHONENUMBER_DEFAULT_FORMAT = "E164" +PHONENUMBER_DEFAULT_REGION = 'CA' + + + MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -72,6 +79,8 @@ TAILWIND_APP_NAME = 'theme' +AUTH_USER_MODEL = 'afbcore.User' + INTERNAL_IPS = [ # Add local IP addresses here for tailwind to work, then run: # * (Dev) `python manage.py tailwind install` diff --git a/afb/afb/urls.py b/afb/afb/urls.py index eb4e42eb..52f1c053 100644 --- a/afb/afb/urls.py +++ b/afb/afb/urls.py @@ -17,13 +17,13 @@ from django.contrib import admin from django.urls import include, path -from afbcore.views import AboutView, ClientCreateView, CreateClientFormView, DashboardView - +from afbcore.views import AboutView, ClientCreateView, CreateClientFormView, DashboardView, MyLoginView +from afbcore.views import ClientRequestView urlpatterns = [ path('admin/', admin.site.urls), - path('dashboard', DashboardView.as_view()), + path('dashboard', DashboardView.as_view(), name='dashboard'), path('', CreateClientFormView.as_view()), @@ -33,5 +33,8 @@ # See https://django-tailwind.readthedocs.io/en/latest/installation.html#configuration path('__reload__/', include('django_browser_reload.urls')), + path('request/', ClientRequestView.as_view(), name='create_request'), + path('login/', MyLoginView.as_view(), name='login'), + path('about/', AboutView.as_view()), ] diff --git a/afb/afb/wsgi.py b/afb/afb/wsgi.py index be55040b..cf57b0a4 100644 --- a/afb/afb/wsgi.py +++ b/afb/afb/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'afb.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "afb.settings") application = get_wsgi_application() diff --git a/afb/afbcore/admin.py b/afb/afbcore/admin.py index 8c38f3f3..47589575 100644 --- a/afb/afbcore/admin.py +++ b/afb/afbcore/admin.py @@ -1,3 +1,12 @@ + from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from .models import Client, Manager, Volunteer, User + +admin.site.register(User, UserAdmin) +admin.site.register(Client) +admin.site.register(Manager) +admin.site.register(Volunteer) + # Register your models here. diff --git a/afb/afbcore/forms/__init__.py b/afb/afbcore/forms/__init__.py index 4793f91b..b33bd833 100644 --- a/afb/afbcore/forms/__init__.py +++ b/afb/afbcore/forms/__init__.py @@ -1,3 +1,10 @@ # +from django import forms + from .client import ClientForm + + +class LoginForm(forms.Form): + username = forms.CharField(max_length=100) + password = forms.CharField(widget=forms.PasswordInput) diff --git a/afb/afbcore/migrations/0001_initial.py b/afb/afbcore/migrations/0001_initial.py index c1eddec0..2634d148 100644 --- a/afb/afbcore/migrations/0001_initial.py +++ b/afb/afbcore/migrations/0001_initial.py @@ -1,16 +1,137 @@ -# Generated by Django 4.2.5 on 2023-09-19 20:35 +# Generated by Django 4.2.5 on 2023-09-22 02:58 +from django.conf import settings +import django.contrib.auth.models +import django.contrib.auth.validators from django.db import migrations, models import django.db.models.deletion +import django.utils.timezone +import phonenumber_field.modelfields import uuid class Migration(migrations.Migration): initial = True - dependencies = [] + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ] operations = [ + migrations.CreateModel( + name="User", + fields=[ + ("password", models.CharField(max_length=128, verbose_name="password")), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "username", + models.CharField( + error_messages={ + "unique": "A user with that username already exists." + }, + help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", + max_length=150, + unique=True, + validators=[ + django.contrib.auth.validators.UnicodeUsernameValidator() + ], + verbose_name="username", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, max_length=254, verbose_name="email address" + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, verbose_name="date joined" + ), + ), + ( + "id", + models.UUIDField( + default=uuid.uuid4, + editable=False, + primary_key=True, + serialize=False, + ), + ), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + }, + managers=[ + ("objects", django.contrib.auth.models.UserManager()), + ], + ), migrations.CreateModel( name="Branch", fields=[ @@ -65,15 +186,19 @@ class Migration(migrations.Migration): serialize=False, ), ), - ("first_name", models.CharField(max_length=255)), - ("last_name", models.CharField(max_length=255)), + ("first_name", models.CharField(max_length=64)), + ("last_name", models.CharField(max_length=64)), ("email", models.EmailField(max_length=254, unique=True)), - ("address", models.CharField(max_length=255)), + ( + "phone_number", + phonenumber_field.modelfields.PhoneNumberField( + blank=True, max_length=20, null=True, region="US" + ), + ), + ("address_verbatim", models.CharField(blank=True, max_length=255)), + ("address", models.CharField(max_length=255, null=True)), ("validated_postal_code", models.CharField(max_length=20, null=True)), ("country", models.CharField(blank=True, max_length=255)), - ("phone_number", models.CharField(max_length=20)), - ("agreed_to_terms", models.BooleanField()), - ("agreed_on_date", models.DateField(blank=True, null=True)), ( "status", models.CharField( @@ -87,13 +212,22 @@ class Migration(migrations.Migration): ), ), ( - "branch", - models.ForeignKey( + "branches", + models.ManyToManyField( + blank=True, related_name="+", to="afbcore.branch" + ), + ), + ( + "user", + models.OneToOneField( on_delete=django.db.models.deletion.DO_NOTHING, - to="afbcore.branch", + to=settings.AUTH_USER_MODEL, ), ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="DeliveryRegion", @@ -208,16 +342,34 @@ class Migration(migrations.Migration): serialize=False, ), ), - ("first_name", models.CharField(max_length=255)), - ("last_name", models.CharField(max_length=255)), - ("email", models.EmailField(max_length=254)), + ("first_name", models.CharField(max_length=64)), + ("last_name", models.CharField(max_length=64)), + ("email", models.EmailField(max_length=254, unique=True)), + ("address_verbatim", models.CharField(blank=True, max_length=255)), + ("address", models.CharField(max_length=255, null=True)), ("phone_number", models.CharField(max_length=20)), ("points_earned", models.IntegerField(default=0)), + ( + "branches", + models.ManyToManyField( + blank=True, related_name="+", to="afbcore.branch" + ), + ), ( "delivery_regions", models.ManyToManyField(to="afbcore.deliveryregion"), ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.DO_NOTHING, + to=settings.AUTH_USER_MODEL, + ), + ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="PetRequest", @@ -233,6 +385,7 @@ class Migration(migrations.Migration): ), ("confirm_address", models.BooleanField()), ("confirm_phone_number", models.BooleanField()), + ("agreed_to_terms", models.BooleanField()), ("method_of_contact", models.CharField(max_length=100)), ("date_requested", models.DateField(auto_now_add=True)), ("safe_drop", models.BooleanField()), @@ -302,10 +455,17 @@ class Migration(migrations.Migration): serialize=False, ), ), - ("first_name", models.CharField(max_length=255)), - ("last_name", models.CharField(max_length=255)), - ("email_address", models.EmailField(max_length=254)), - ("phone_number", models.CharField(max_length=20)), + ("first_name", models.CharField(max_length=64)), + ("last_name", models.CharField(max_length=64)), + ("email", models.EmailField(max_length=254, unique=True)), + ( + "phone_number", + phonenumber_field.modelfields.PhoneNumberField( + blank=True, max_length=20, null=True, region="US" + ), + ), + ("address_verbatim", models.CharField(blank=True, max_length=255)), + ("address", models.CharField(max_length=255, null=True)), ("role_level", models.IntegerField()), ( "branches", @@ -313,7 +473,17 @@ class Migration(migrations.Migration): blank=True, related_name="+", to="afbcore.branch" ), ), + ( + "user", + models.OneToOneField( + on_delete=django.db.models.deletion.DO_NOTHING, + to=settings.AUTH_USER_MODEL, + ), + ), ], + options={ + "abstract": False, + }, ), migrations.CreateModel( name="Delivery", diff --git a/afb/afbcore/models/__init__.py b/afb/afbcore/models/__init__.py index 3ceb18d3..51f49d66 100644 --- a/afb/afbcore/models/__init__.py +++ b/afb/afbcore/models/__init__.py @@ -3,9 +3,7 @@ from django.db import models from .branch import Branch # noqa -from .manager import Manager -from .client import Client -from .volunteer import Volunteer +from .users import Client, Manager, Volunteer, User from .pet_request import PetRequest from .pet import Pet from .food_available import FoodAvailable diff --git a/afb/afbcore/models/pet_request.py b/afb/afbcore/models/pet_request.py index 3abef4c2..8acda1e6 100644 --- a/afb/afbcore/models/pet_request.py +++ b/afb/afbcore/models/pet_request.py @@ -35,6 +35,9 @@ class PetRequest(models.Model): # Yes/No - No requires them to update phone number (validate format) confirm_phone_number = models.BooleanField() + # Agree to Terms and Conditions + agreed_to_terms = models.BooleanField() + # Text or Phone method_of_contact = models.CharField(max_length=100) @@ -62,6 +65,7 @@ class PetRequest(models.Model): # Not sure what to call this one, but if the volunteer has an issue with the client - they are rude or aggressive for example, can we allow the driver to mark the client as suspended and admin to review? needs_review = models.BooleanField() + def __str__(self): return f"Pet Request {self.id}" diff --git a/afb/afbcore/models/profile.py b/afb/afbcore/models/profile.py deleted file mode 100644 index e69de29b..00000000 diff --git a/afb/afbcore/models/users/__init__.py b/afb/afbcore/models/users/__init__.py new file mode 100644 index 00000000..60808e88 --- /dev/null +++ b/afb/afbcore/models/users/__init__.py @@ -0,0 +1,7 @@ + + +from .client import Client +from .manager import Manager +from .volunteer import Volunteer +from .base_profile import BaseProfile +from .user import User diff --git a/afb/afbcore/models/users/base_profile.py b/afb/afbcore/models/users/base_profile.py new file mode 100644 index 00000000..1d9bf16a --- /dev/null +++ b/afb/afbcore/models/users/base_profile.py @@ -0,0 +1,59 @@ + +import uuid + +from django.db import models + +# https://django-phonenumber-field.readthedocs.io/en/latest/reference.html#model-field +# https://github.com/google/libphonenumber/blob/master/FALSEHOODS.md +from phonenumber_field.modelfields import PhoneNumberField + +from .user import User + +# Define default arguments for ManyToManyField +many_to_many_defaults = { + 'related_name': '+', + 'blank': True, + 'symmetrical': False, +} + + +class BaseProfile(models.Model): + + class Meta: + abstract = True + + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + + user = models.OneToOneField(User, on_delete=models.DO_NOTHING) + + # Usually just one, but can be multiple + branches = models.ManyToManyField("Branch", **many_to_many_defaults) + + # First Name + first_name = models.CharField(max_length=64) + + # Last Name + last_name = models.CharField(max_length=64) + + # Email - Unique - don't allow duplicates. + email = models.EmailField(unique=True) + + # Phone - Validate format - numbers only + phone_number = PhoneNumberField( + blank=True, + null=True, + region='US', + max_length=20, + ) + + # We allow free form text entry but store the validated + # address in the `address` field. These fields should + # generally be updated together. + # + # e.g. A client may enter "123 Main St" and another + # client may enter "123 Main Street" + address_verbatim = models.CharField(max_length=255, blank=True) + + # Validated Address + # i.e. An address from Canada Post or Google Maps + address = models.CharField(max_length=255, null=True) diff --git a/afb/afbcore/models/client.py b/afb/afbcore/models/users/client.py similarity index 69% rename from afb/afbcore/models/client.py rename to afb/afbcore/models/users/client.py index fe92d33f..2fa5e0b6 100644 --- a/afb/afbcore/models/client.py +++ b/afb/afbcore/models/users/client.py @@ -1,8 +1,11 @@ import uuid + from django.db import models +from django.contrib.auth.models import AbstractUser from django.urls import reverse +from .base_profile import BaseProfile # Status - Active, On Hold, Banned STATUS_CHOICES = [ @@ -11,29 +14,19 @@ ('banned', 'Banned'), ] -class Client(models.Model): + + +class Client(BaseProfile): """ Clients could try to scam and create duplicate accounts to circumvent frequency, change pet info, etc. We need to do our best to ensure each account is unique. """ - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - - branch = models.ForeignKey('Branch', on_delete=models.DO_NOTHING) - - # First Name - first_name = models.CharField(max_length=255) - - # Last Name - last_name = models.CharField(max_length=255) - - # Email - Unique - don't allow duplicates. - email = models.EmailField(unique=True) # Address - If address is duplicate to another clients, both accounts need to be placed on hold and manually reviewed/approved bc people are scammers. # Has to be a validated address (google?) and not permitted to be overwritten. The last amount of free form text entry as possible. # You'd be amazed how many clients don't know their postal code and we route by postal code sooooo - address = models.CharField(max_length=255) + # Postal/Zip Code - Has to be a validated address (google?) and not permitted to be overwritten. The last amount of free form text entry as possible. # You'd be amazed how many clients don't know their postal code and we route by postal code sooooo @@ -42,15 +35,6 @@ class Client(models.Model): # Country - I don't know if we need this but google addresses populate country too. It may be useful for analytics country = models.CharField(max_length=255, blank=True) - # Phone - Validate format - numbers only - phone_number = models.CharField(max_length=20) - - # Agree to Terms and Conditions - agreed_to_terms = models.BooleanField() - - # Agreed on date - Yes/No - agreed_on_date = models.DateField(null=True, blank=True) - status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active') diff --git a/afb/afbcore/models/manager.py b/afb/afbcore/models/users/manager.py similarity index 79% rename from afb/afbcore/models/manager.py rename to afb/afbcore/models/users/manager.py index 6e3be547..c9f4b5c5 100644 --- a/afb/afbcore/models/manager.py +++ b/afb/afbcore/models/users/manager.py @@ -3,24 +3,12 @@ from django.db import models +from .base_profile import BaseProfile -class Manager(models.Model): + +class Manager(BaseProfile): """ """ - # Define default arguments for ManyToManyField - many_to_many_defaults = { - 'related_name': '+', - 'blank': True, - 'symmetrical': False, - } - - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - first_name = models.CharField(max_length=255) - last_name = models.CharField(max_length=255) - email_address = models.EmailField() - phone_number = models.CharField(max_length=20) - - branches = models.ManyToManyField("Branch", **many_to_many_defaults)# Usually just one, but can be multiple role_level = models.IntegerField() diff --git a/afb/afbcore/models/users/user.py b/afb/afbcore/models/users/user.py new file mode 100644 index 00000000..909f6d35 --- /dev/null +++ b/afb/afbcore/models/users/user.py @@ -0,0 +1,9 @@ + + +import uuid + +from django.db import models +from django.contrib.auth.models import AbstractUser + +class User(AbstractUser): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/afb/afbcore/models/volunteer.py b/afb/afbcore/models/users/volunteer.py similarity index 80% rename from afb/afbcore/models/volunteer.py rename to afb/afbcore/models/users/volunteer.py index 1c1b4dd7..34ac5598 100644 --- a/afb/afbcore/models/volunteer.py +++ b/afb/afbcore/models/users/volunteer.py @@ -2,12 +2,10 @@ import uuid from django.db import models -class Volunteer(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) +from .base_profile import BaseProfile - first_name = models.CharField(max_length=255) - last_name = models.CharField(max_length=255) - email = models.EmailField() + +class Volunteer(BaseProfile): # Would love for the client and volunteer to be able to communicate through the software - and not have the volunteer have to use their personal phone to arrange delivery. However, we, as admin, need their phone number phone_number = models.CharField(max_length=20) diff --git a/afb/afbcore/templates/afbcore/client_request.html b/afb/afbcore/templates/afbcore/client_request.html new file mode 100644 index 00000000..6931a3e7 --- /dev/null +++ b/afb/afbcore/templates/afbcore/client_request.html @@ -0,0 +1,13 @@ + +{% extends 'appshell.html' %} + +{% load tailwind_filters %} + + +{% block content %} + +Make a request + +{{form | crispy}} + +{% endblock %} diff --git a/afb/afbcore/templates/afbcore/login.html b/afb/afbcore/templates/afbcore/login.html new file mode 100644 index 00000000..93febf51 --- /dev/null +++ b/afb/afbcore/templates/afbcore/login.html @@ -0,0 +1,10 @@ +{% extends 'appshell.html' %} + +{% block content %} +

Login

+
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock %} diff --git a/afb/theme/templates/appshell.html b/afb/afbcore/templates/appshell.html similarity index 94% rename from afb/theme/templates/appshell.html rename to afb/afbcore/templates/appshell.html index ed1e002b..fde60108 100644 --- a/afb/theme/templates/appshell.html +++ b/afb/afbcore/templates/appshell.html @@ -56,6 +56,7 @@
Dashboard + Request About
@@ -105,7 +106,7 @@ Your Profile Settings - Sign out + Sign In @@ -133,6 +134,7 @@
Dashboard + Request About
@@ -155,7 +157,8 @@
Your Profile Settings - Sign out + Sign In +
diff --git a/afb/afbcore/views/__init__.py b/afb/afbcore/views/__init__.py index 003fee7b..cf0b9e34 100644 --- a/afb/afbcore/views/__init__.py +++ b/afb/afbcore/views/__init__.py @@ -3,3 +3,10 @@ from .client import * from .dashboard import (ClientDashboardView, DashboardRouterView, ManagerDashboardView, VolunteerDashboardView) + +from django.contrib.auth.views import LoginView +from django.urls import reverse_lazy + +class MyLoginView(LoginView): + template_name = 'afbcore/login.html' + success_url = reverse_lazy('afbcore:dashboard') diff --git a/afb/afbcore/views/client.py b/afb/afbcore/views/client.py index 23104e03..2f29cfad 100644 --- a/afb/afbcore/views/client.py +++ b/afb/afbcore/views/client.py @@ -26,3 +26,7 @@ class CreateClientFormView(FormView): class ClientCreateView(CreateView): model = Client fields = ["first_name", "last_name", "email", "address", "phone_number", "status"] + + +class ClientRequestView(TemplateView): + template_name = "afbcore/client_request.html" diff --git a/afb/requirements.in b/afb/requirements.in new file mode 100644 index 00000000..dea8c3c4 --- /dev/null +++ b/afb/requirements.in @@ -0,0 +1,37 @@ +# +# Requirements file for afb-requests. Modify this file to +# include the packages you want to install. Then, run: +# +# pip-compile --output-file requirements.txt requirements.in +# +# This will generate a new requirements.txt file with the +# latest versions of the packages listed here. +# +# You can also run: +# +# pip-compile --upgrade --output-file requirements.txt requirements.in +# +# to upgrade all packages to their latest versions. +# +# See: https://github.com/jazzband/pip-tools +# + +Django==4.2.5 +django-tailwind[reload] +django-unfold +django-crispy-forms +crispy-tailwind +pillow + +phonenumbers +django-phonenumber-field + +pip-tools +pre-commit + +# Copilot suggestions +# django-extensions +# django-allauth +# django-address +# django-money +# django-countries diff --git a/afb/requirements.txt b/afb/requirements.txt index bf629613..8c366a6d 100644 --- a/afb/requirements.txt +++ b/afb/requirements.txt @@ -1,16 +1,74 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --output-file=requirements.txt requirements.in +# +asgiref==3.7.2 + # via django +build==1.0.3 + # via pip-tools +cfgv==3.4.0 + # via pre-commit +click==8.1.7 + # via pip-tools +crispy-tailwind==0.5.0 + # via -r requirements.in +distlib==0.3.7 + # via virtualenv +django==4.2.5 + # via + # -r requirements.in + # django-browser-reload + # django-crispy-forms + # django-phonenumber-field + # django-tailwind + # django-unfold +django-browser-reload==1.11.0 + # via django-tailwind +django-crispy-forms==2.0 + # via + # -r requirements.in + # crispy-tailwind +django-phonenumber-field==7.1.0 + # via -r requirements.in +django-tailwind[reload]==3.6.0 + # via -r requirements.in +django-unfold==0.10.0 + # via -r requirements.in +filelock==3.12.4 + # via virtualenv +identify==2.5.29 + # via pre-commit +importlib-metadata==6.7.0 + # via django-unfold +nodeenv==1.8.0 + # via pre-commit +packaging==23.1 + # via build +phonenumbers==8.13.22 + # via -r requirements.in +pillow==10.0.1 + # via -r requirements.in +pip-tools==7.3.0 + # via -r requirements.in +platformdirs==3.10.0 + # via virtualenv +pre-commit==3.4.0 + # via -r requirements.in +pyproject-hooks==1.0.0 + # via build +pyyaml==6.0.1 + # via pre-commit +sqlparse==0.4.4 + # via django +virtualenv==20.24.5 + # via pre-commit +wheel==0.41.2 + # via pip-tools +zipp==3.17.0 + # via importlib-metadata - -Django==4.2.5 -django-tailwind[reload] -django-unfold -django-crispy-forms -crispy-tailwind -pillow - -# Copilot suggestions -django-extensions -django-allauth -django-countries -django-phonenumber-field -django-address -# django-money +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools