Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[run]
omit =
*/migrations/*
*/tests/*
config/*
manage.py
base
relative_files = true
33 changes: 33 additions & 0 deletions .github/workflows/dev-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: AWS Deployment

on:
push:
branches:
- main

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout Repository
uses: actions/checkout@v2

- name: Configure AWS CLI
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: SSH into AWS Instance
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.AWS_INSTANCE_IP }}
username: ${{ secrets.AWS_INSTANCE_USERNAME }}
key: ${{ secrets.AWS_INSTANCE_PRIVATE_KEY }}
port: ${{ secrets.AWS_INSTANCE_SSH_PORT }}
script: |
cd /home/ubuntu/eventsradar-api-v2/
git pull origin main
docker compose restart
46 changes: 46 additions & 0 deletions .github/workflows/pyunittest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI

on:
pull_request:
push:
branches:
- "main"

jobs:
test:
name: Run tests & display coverage
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
# Step 1: Checkout the code
- uses: actions/checkout@v4

# Step 2: Set up Python and install dependencies
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov # Install pytest and coverage plugin

# Step 3: Run pytest with coverage
- name: Run tests with pytest
run: |
pytest --cov=. # Run pytest and generate coverage for the entire project

# Step 4: Generate a coverage report
- name: Generate coverage report
run: |
coverage xml # Generates an XML report

# Step 5: Post coverage comment
- name: Coverage comment
uses: py-cov-action/python-coverage-comment-action@v3
with:
GITHUB_TOKEN: ${{ github.token }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ node_modules
.next
admin/.next
build/
media/
.coverage
htmlcov
.coverage.*

.env*
26 changes: 15 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
FROM python:3
# Use the official Python image
FROM python:3.10.12

# Set environment variables for Python optimizations
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# Set the working directory in the container
WORKDIR /code

# Install dependencies
COPY requirements.txt /code/

# Copy the project code into the container
COPY . /code/
# Install GDAL and libpq dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libpq-dev \
gdal-bin \
libgdal-dev \
&& rm -rf /var/lib/apt/lists/*

# Install GDAL dependencies
RUN apt-get update
# Copy the requirements file to the working directory
COPY requirements.txt /code/

# Upgrade pip and install Python dependencies
RUN pip install --upgrade pip

# Install dependencies
RUN pip install setuptools
RUN pip install --no-cache-dir -r requirements.txt


# Copy the rest of the project code into the container
COPY . /code/

# Expose the port on which Gunicorn will run
EXPOSE 8000
34 changes: 10 additions & 24 deletions authentication/admin.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import re

from django.contrib import admin
from django.utils.safestring import mark_safe

from authentication.models import User

Expand All @@ -9,48 +8,35 @@ def is_sha256_hash(text):
# Check if the text contains 'sha256' substring
if 'sha256' not in text:
return False

# Check if the hash has the expected length and format
sha256_hash_length = 64 # SHA-256 produces a 64-character hex string
sha256_hash_pattern = r'[a-fA-F0-9]{64}' # 64 hex characters

# Find the portion of the string that could be the SHA-256 hash
possible_hash = re.search(sha256_hash_pattern, text)

# If there's a match and the whole possible hash is the correct length, it's likely a SHA-256 hash
if possible_hash and len(possible_hash.group()) == sha256_hash_length:
return True

return False
return True


@admin.register(User)
class UserAdmin(admin.ModelAdmin):
list_display = ('email', 'full_name', 'mobile_number', 'is_active', 'is_staff', 'is_superuser')
list_filter = ('is_active', 'is_staff', 'is_superuser')
search_fields = ('email', 'full_name', 'mobile_number')
list_display = ('email', 'full_name', 'is_active', 'is_staff', 'is_superuser', 'avatar')
list_filter = ('is_active', 'is_staff', 'is_superuser', 'groups', )
search_fields = ('email', 'full_name',)
ordering = ('email',)
fieldsets = (
(None, {'fields': ('email', 'password')}),
('Personal info', {'fields': ('full_name', 'mobile_number', 'section')}),
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser')}),
('Personal info', {'fields': ('full_name', 'profile')}),
('Permissions', {'fields': ('is_active', 'is_staff', 'is_superuser', 'groups')}),
('Important dates', {'fields': ('last_login', 'date_joined')}),
)

def save_model(self, request, obj, form, change):
# Get the password from the form if provided
password = form.cleaned_data.get('password')
if password:
print("Password: ", password)
if not is_sha256_hash(password):
print("setting password")
# If the password is not a hash, hash it
obj.set_password(password)
# If the password is already a hash, don't rehash it
else:
obj.password = password
elif 'password' in form.changed_data:
# If password field is modified but no new password is provided, ignore the password field
form.cleaned_data.pop('password', None)

super().save_model(request, obj, form, change)

def avatar(self, obj):
return mark_safe(f'<img src="{obj.profile}" width="50" height="50" />')
35 changes: 24 additions & 11 deletions authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Generated by Django 5.1.1 on 2024-09-16 10:23
# Generated by Django 4.2.16 on 2024-10-07 15:06

import authentication.models
import django.utils.timezone
from django.db import migrations, models

import authentication.models


class Migration(migrations.Migration):

initial = True

dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
('auth', '0001_initial'),
]

operations = [
Expand All @@ -19,18 +19,31 @@ class Migration(migrations.Migration):
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')),
('id', models.CharField(default=authentication.models.generate_unique_code, max_length=255, primary_key=True, serialize=False)),
('is_superuser', models.BooleanField(default=False,
help_text='Designates that this user has all permissions without explicitly assigning them.',
verbose_name='superuser status')),
('id',
models.CharField(default=authentication.models.generate_unique_code, max_length=255, primary_key=True,
serialize=False)),
('full_name', models.CharField(blank=True, max_length=255, verbose_name='full name')),
('email', models.EmailField(blank=True, max_length=254, unique=True, 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')),
('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')),
('created_at', models.DateTimeField(auto_now_add=True, null=True)),
('updated_at', models.DateTimeField(auto_now=True, null=True)),
('mobile_number', models.CharField(blank=True, max_length=20, null=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')),
('profile', models.URLField(blank=True, max_length=255, null=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',
Expand Down
18 changes: 18 additions & 0 deletions authentication/migrations/0002_alter_user_is_active.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.16 on 2024-10-14 05:24

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('authentication', '0001_initial'),
]

operations = [
migrations.AlterField(
model_name='user',
name='is_active',
field=models.BooleanField(default=True, help_text='Designates whether this user should be treated as active.Unselect this instead of deleting accounts.', verbose_name='active'),
),
]
9 changes: 6 additions & 3 deletions authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class BaseUser(AbstractBaseUser, PermissionsMixin):
_("active"),
default=True,
help_text=_(
"Designates whether this user should be treated as active. "
"Designates whether this user should be treated as active."
"Unselect this instead of deleting accounts."
),
)
Expand Down Expand Up @@ -107,12 +107,15 @@ def email_user(self, subject, message, from_email=None, **kwargs):
mail.EmailMessage(
subject, message, from_email, [
self.email], connection=connection, **kwargs).send()
# send_mail(subject, message, from_email, [self.email], **kwargs)
# send_mail(subject, message, from_email, [self.email], **kwargs)


class User(BaseUser):
"""
this is a custom user model extending BaseUser
"""

mobile_number = models.CharField(max_length=20, blank=True, null=True)
profile = models.URLField(max_length=255, blank=True, null=True)

def __str__(self):
return self.full_name
Loading
Loading