Skip to content

Commit 6180e19

Browse files
modified account/models.py by adding roles and departments
1 parent 79652d7 commit 6180e19

File tree

3 files changed

+171
-1
lines changed

3 files changed

+171
-1
lines changed

account/models.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from django.conf import settings
2+
from django.contrib.auth.base_user import BaseUserManager
3+
from django.contrib.auth.models import PermissionsMixin, AbstractUser
4+
from django.core.exceptions import ValidationError
5+
from django.core.validators import RegexValidator
6+
from django.db import models
7+
from django.utils import timezone
8+
from django.utils.translation import gettext_lazy as _
9+
from simple_history.models import HistoricalRecords
10+
11+
from .utils import profile_upload_to_unique
12+
13+
14+
class Profile(models.Model):
15+
"""
16+
Model representing a user's profile.
17+
Connects to income, expenses, and allows for personal data storage.
18+
"""
19+
user = models.OneToOneField(
20+
settings.AUTH_USER_MODEL,
21+
on_delete=models.CASCADE,
22+
related_name="profile"
23+
)
24+
balance = models.DecimalField(
25+
max_digits=10,
26+
decimal_places=2,
27+
default=0.0,
28+
help_text="User's current balance."
29+
)
30+
date_created = models.DateTimeField(auto_now_add=True)
31+
profile_pic = models.ImageField(
32+
upload_to=profile_upload_to_unique,
33+
blank=True,
34+
null=True,
35+
help_text="User's profile picture."
36+
)
37+
38+
# Add historical records field to track changes
39+
history = HistoricalRecords()
40+
41+
def __str__(self):
42+
"""String representation of the profile object, displaying the associated user's username."""
43+
return f'Profile of {self.user}'
44+
45+
46+
class Department(models.Model):
47+
name = models.CharField(max_length=100, unique=True)
48+
49+
def __str__(self):
50+
return self.name
51+
52+
53+
class Role(models.Model):
54+
name = models.CharField(max_length=100, unique=True)
55+
description = models.TextField(blank=True)
56+
57+
def __str__(self):
58+
return self.name
59+
60+
61+
# Custom User Manager
62+
class UserManager(BaseUserManager):
63+
def create_user(self, email, password=None, **extra_fields):
64+
if not email:
65+
raise ValueError(_('The Email field must be set'))
66+
email = self.normalize_email(email)
67+
user = self.model(email=email, **extra_fields)
68+
user.set_password(password)
69+
user.save(using=self._db)
70+
return user
71+
72+
def create_superuser(self, email, password=None, **extra_fields):
73+
extra_fields.setdefault('is_staff', True)
74+
extra_fields.setdefault('is_superuser', True)
75+
extra_fields.setdefault('is_active', True)
76+
77+
if extra_fields.get('is_staff') is not True:
78+
raise ValueError(_('Superuser must have is_staff=True.'))
79+
if extra_fields.get('is_superuser') is not True:
80+
raise ValueError(_('Superuser must have is_superuser=True.'))
81+
82+
return self.create_user(email, password, **extra_fields)
83+
84+
85+
# Custom User Model
86+
class UserAccount(AbstractUser, PermissionsMixin):
87+
email = models.EmailField(_('email address'), unique=True)
88+
username = models.CharField(_('username'), max_length=30, unique=True, blank=False,
89+
help_text="User's unique username",
90+
validators=[
91+
RegexValidator(
92+
regex=r'^[\w-]+$',
93+
message=_(
94+
"Username can only contain letters, numbers, underscores, or hyphens.")
95+
)
96+
]
97+
)
98+
phone_number = models.CharField(_('phone number'), max_length=15, unique=True, null=True, blank=True,
99+
validators=[
100+
RegexValidator(
101+
regex=r'^\+?1?\d{9,15}$',
102+
message=_(
103+
"Phone number must be entered in the format: '+999999999'. Up to 15 digits allowed."
104+
),
105+
)
106+
], )
107+
first_name = models.CharField(_('first name'), max_length=30, blank=True)
108+
last_name = models.CharField(_('last name'), max_length=30, blank=True)
109+
profile_image = models.ImageField(_('profile image'), upload_to='profile_images/', null=True, blank=True)
110+
bio = models.TextField(_('bio'), max_length=500, blank=True)
111+
last_login_ip = models.GenericIPAddressField(_('last login IP'), null=True, blank=True)
112+
last_login = models.DateTimeField(_('last login'), auto_now=True)
113+
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
114+
115+
is_active = models.BooleanField(_('active'), default=True)
116+
is_staff = models.BooleanField(_('staff status'), default=False)
117+
is_manager = models.BooleanField(_('manager status'), default=False)
118+
is_admin = models.BooleanField(_('admin status'), default=False)
119+
roles = models.ManyToManyField(Role, related_name='users', blank=True)
120+
department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True)
121+
122+
objects = UserManager()
123+
124+
USERNAME_FIELD = 'email'
125+
REQUIRED_FIELDS = ['username', 'phone_number', 'first_name', 'last_name']
126+
127+
@property
128+
def is_staff_effective(self):
129+
# Check if the user has an associated employee profile (and therefore system access)
130+
return hasattr(self, 'employee_profile')
131+
132+
@property
133+
def name(self):
134+
return f'{self.first_name} {self.last_name}'
135+
136+
def __str__(self):
137+
return self.username or self.email
138+
139+
class Meta:
140+
verbose_name = _('user')
141+
verbose_name_plural = _('users')
142+
ordering = ['-pk']
143+
144+
145+
class Employee(models.Model):
146+
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='employee_profile')
147+
# Department is mandatory (null is False by default)
148+
department = models.ForeignKey(Department, on_delete=models.PROTECT)
149+
# Roles is a ManyToManyField; however, Django doesn't enforce non-empty assignments by default.
150+
roles = models.ManyToManyField(Role)
151+
employee_id = models.CharField(max_length=50, unique=True)
152+
hire_date = models.DateField()
153+
154+
def clean(self):
155+
# Ensure that department is provided (should always be, since null is not allowed)
156+
if not self.department:
157+
raise ValidationError("Department must be provided.")
158+
159+
# Check that at least one role is assigned. Be aware that since roles is a M2M field,
160+
# this check may need to happen after the instance is saved.
161+
if not self.pk or self.roles.count() == 0:
162+
raise ValidationError("At least one role must be assigned to the employee.")
163+
164+
def save(self, *args, **kwargs):
165+
# Perform full clean before saving to ensure all validations are enforced.
166+
self.full_clean()
167+
super().save(*args, **kwargs)
168+
169+
def __str__(self):
170+
return f"{self.user.username} ({self.employee_id})"

requirements.txt

1.8 KB
Binary file not shown.

shop/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.core.validators import MinValueValidator, MaxValueValidator
55
from django.db import models
66
from django.urls import reverse
7-
from django.utils.text import slugify
7+
from slugify import slugify
88
from taggit.managers import TaggableManager
99

1010
from .custom_taggit import CustomTaggedItem

0 commit comments

Comments
 (0)