Skip to content

Commit

Permalink
Added accounts and login features (#29)
Browse files Browse the repository at this point in the history
* Added accounts and login features

* Add tests and update requirements for Postgres binary

* Run pre-commit hooks on all files

Signed-off-by: Nishant Nayak <nishantnayak2001@gmail.com>

* Made ui changes to forms

* Update DaisyUI to 4.x and fix some styles

Signed-off-by: Nishant Nayak <nishantnayak2001@gmail.com>

* Update Dockerfiles and Shell scripts according to linters

Signed-off-by: Nishant Nayak <nishantnayak2001@gmail.com>

---------

Signed-off-by: Nishant Nayak <nishantnayak2001@gmail.com>
Co-authored-by: Nishant Nayak <nishantnayak2001@gmail.com>
  • Loading branch information
anirudhprabhakaran3 and nishant-nayak authored Nov 25, 2023
1 parent ce37ba4 commit 1e7f0ca
Show file tree
Hide file tree
Showing 25 changed files with 736 additions and 34 deletions.
25 changes: 13 additions & 12 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -42,20 +42,21 @@ repos:
hooks:
- id: reorder-python-imports

- repo: https://github.com/PyCQA/bandit
rev: 1.7.5
hooks:
- id: bandit
# Use the following hooks locally to check for issues. These will mostly always fail on CI.
# - repo: https://github.com/PyCQA/bandit
# rev: 1.7.5
# hooks:
# - id: bandit

- repo: https://github.com/hadolint/hadolint
rev: v2.12.0
hooks:
- id: hadolint-docker
# - repo: https://github.com/hadolint/hadolint
# rev: v2.12.0
# hooks:
# - id: hadolint-docker

- repo: https://github.com/shellcheck-py/shellcheck-py
rev: v0.9.0.6
hooks:
- id: shellcheck
# - repo: https://github.com/shellcheck-py/shellcheck-py
# rev: v0.9.0.6
# hooks:
# - id: shellcheck

- repo: https://github.com/ecugol/pre-commit-hooks-django
rev: v0.4.0 # Use the ref you want to point at
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ RUN mkdir /corpus
WORKDIR /corpus

# Install dependencies
RUN apt update && apt install -y gcc libpq-dev sqlite3
RUN apt-get update && apt-get install -y gcc libpq-dev sqlite3

# Install Python dependencies
COPY corpus/requirements.txt .
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
RUN pip install python-dotenv==1.0.0
RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install --no-cache-dir python-dotenv==1.0.0

# Set environment variables
ENV LIVECYCLE 1
Expand Down
6 changes: 3 additions & 3 deletions corpus/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ WORKDIR /corpus
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

RUN apt update && apt install -y gcc libpq-dev
RUN apt-get update && apt-get install -y gcc libpq-dev

COPY requirements.txt .
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
RUN pip install --no-cache-dir --upgrade pip
RUN pip install --no-cache-dir -r requirements.txt

COPY . .
RUN chmod +x ./start.sh
Expand Down
Empty file added corpus/accounts/__init__.py
Empty file.
52 changes: 52 additions & 0 deletions corpus/accounts/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import User

# Register your models here.


class CorpusUserAdmin(UserAdmin):
fieldsets = (
(None, {"fields": ("email", "password")}),
(
"Personal Info",
{"fields": ("first_name", "last_name", "phone_no", "gender")},
),
(
"Permissions",
{
"fields": (
"is_active",
"is_staff",
"is_superuser",
"groups",
"user_permissions",
)
},
),
("Important dates", {"fields": ("last_login", "date_joined")}),
)
add_fieldsets = (
(
None,
{
"classes": ("wide",),
"fields": (
"email",
"first_name",
"last_name",
"phone_no",
"gender",
"password1",
"password2",
),
},
),
)
list_display = ("email", "first_name", "last_name", "phone_no", "gender")
search_fields = ("email", "first_name", "last_name", "phone_no")
ordering = ("email",)


admin.site.register(User, CorpusUserAdmin)
6 changes: 6 additions & 0 deletions corpus/accounts/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.apps import AppConfig


class AccountsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "accounts"
22 changes: 22 additions & 0 deletions corpus/accounts/backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import ModelBackend


class CorpusAuthBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
try:
user = UserModel.objects.get(email=username)
except UserModel.DoesNotExist:
return None
else:
if user.check_password(password):
return user
return None

def get_user(self, user_id):
UserModel = get_user_model()
try:
return UserModel.objects.get(pk=user_id)
except UserModel.DoesNotExist:
return None
64 changes: 64 additions & 0 deletions corpus/accounts/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from django import forms
from django.contrib.auth.forms import AuthenticationForm
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth.forms import UserCreationForm

from .models import GENDERS
from .models import User


class CorpusCreationForm(UserCreationForm):
phone_no = forms.CharField(required=True, max_length=10)
gender = forms.ChoiceField(choices=GENDERS, required=True)
first_name = forms.CharField(max_length=30, required=True, help_text="Required.")

error_css_class = "text-sm text-error"

class Meta:
model = User
fields = [
"first_name",
"last_name",
"phone_no",
"gender",
"email",
"password1",
"password2",
]

def __init__(self, *args, **kwargs):
super(CorpusCreationForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs[
"class"
] = "mt-1 block w-full rounded-md border-base-800 text-black shadow-sm focus:border-primary focus:ring focus:ring-primary-200 focus:ring-opacity-50" # noqa: E501


class CorpusChangeForm(UserChangeForm):
phone_no = forms.CharField(required=True)
gender = forms.ChoiceField(choices=GENDERS, required=True)

class Meta:
model = User
fields = [
"first_name",
"last_name",
"phone_no",
"gender",
"email",
]


class CorpusLoginForm(AuthenticationForm):
def __init__(self, *args, **kwargs):
super(CorpusLoginForm, self).__init__(*args, **kwargs)
for visible in self.visible_fields():
visible.field.widget.attrs[
"class"
] = "mt-1 block w-full rounded-md border-base-800 text-black shadow-sm focus:border-primary focus:ring focus:ring-primary-200 focus:ring-opacity-50" # noqa: E501

def clean_username(self):
username = self.cleaned_data.get("username")
if username:
username = username.lower()
return username
120 changes: 120 additions & 0 deletions corpus/accounts/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Generated by Django 4.2.4 on 2023-09-07 15:06
import django.utils.timezone
from django.db import migrations
from django.db import models


class Migration(migrations.Migration):

initial = True

dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
]

operations = [
migrations.CreateModel(
name="User",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("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",
),
),
(
"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"
),
),
(
"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"
),
),
("phone_no", models.CharField(max_length=13, unique=True)),
(
"gender",
models.CharField(
choices=[
("M", "Male"),
("F", "Female"),
("O", "Other"),
("N", "Prefer not to disclose"),
],
max_length=1,
),
),
("email", models.EmailField(max_length=254, unique=True)),
(
"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,
},
),
]
Empty file.
54 changes: 54 additions & 0 deletions corpus/accounts/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.contrib.auth.base_user import BaseUserManager
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _

# Create your models here.

GENDERS = [
("M", "Male"),
("F", "Female"),
("O", "Other"),
("N", "Prefer not to disclose"),
]


class UserManager(BaseUserManager):
def create_user(self, email, password, **kwargs):
if not email:
raise ValueError(_("Email must be set."))

email = self.normalize_email(email)
user = self.model(email=email, **kwargs)
user.set_password(password)
user.save()
return user

def create_superuser(self, email, password, **kwargs):
kwargs.setdefault("is_staff", True)
kwargs.setdefault("is_superuser", True)
kwargs.setdefault("is_active", True)

if kwargs.get("is_staff") is not True:
raise ValueError(_("Superuser must have is_staff=True."))
if kwargs.get("is_superuser") is not True:
raise ValueError(_("Superuser must have is_superuser=True."))
return self.create_user(email, password, **kwargs)

def users(self):
return self.filter(is_active=True)


class User(AbstractUser):
username = None
phone_no = models.CharField(max_length=13, unique=True, blank=False, null=False)
gender = models.CharField(max_length=1, choices=GENDERS, blank=False, null=False)
email = models.EmailField(unique=True, blank=False, null=False)

USERNAME_FIELD = "email"
REQUIRED_FIELDS = []

objects = UserManager()

def __str__(self):
return f"{self.first_name} {self.last_name}"
Loading

0 comments on commit 1e7f0ca

Please sign in to comment.