I'm trying to build a simple ToDo app in django:
A 'box' contains multiple 'tasks', has an 'owner' (the person who created the box and has administrative privileges) and multiple 'users' (persons who can access the box and add/edit tasks).
I want to automatically add the owner to the users set. I've tried this multiple ways:
Overwriting the save():
class Box(models.Model):
owner = models.ForeignKey(User, related_name='boxes_owned', on_delete=models.CASCADE, null=True, blank=False)
users = models.ManyToManyField(User, related_name='boxes_assigned')
title = models.CharField(max_length=50)
description = models.TextField(null=True, blank=True)
def save(self):
super().save()
self.users.add(self.owner)
This does not work and neither does working with a post-save signal.
#receiver(post_save, sender=Box)
def update_box(sender, instance, **kwargs):
instance.users.add(instance.owner)
The owner won't be added to the users set.
What am I doing wrong, how can I fix this?
Is this the right approach at all?
EDIT:
It seems like the owner is added correctly to users by the save function as well as by the update_box function. At least that is what the debugger tells me. However, when I try to edit the box afterwards the owner is not in the users array.
Are there any events after post_save that could mess with my relationships?
Related
I am trying to implement a relationship between three tables; User Project and Permissions
I already have a ManyToMany relationship between User and Project
class CustomUser(AbstractUser):
username = models.CharField(unique=True, max_length=20, null=False, blank=False)
// etc...
class Project(models.Model):
user = models.ManyToManyField(CustomUser, related_name="projects")
Now I am trying to add the Permission Table. So that User can take different roles on different project.
class Permissions(models.Model):
can_read = models.BooleanField(default=True)
can_read_and_edit = models.BooleanField(default=False)
// etc...
What is the best approach on this situation. Should I Add Permission table into manytomany relation as a third table? Or is there a better way to achieve this
You can use a custom through model to add details to the Many-to-many relation like this:
class CustomUser(AbstractUser):
pass
class Project(models.Model):
users = models.ManyToManyField(CustomUser, through='ProjectDetails')
class Permission(models.Model):
pass
class ProjectDetails(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
project = models.ForeignKey(Project, on_delete=models.CASCADE)
permission = models.ForeignKey(Permission, on_delete=models.CASCADE)
This way, each user for a project can be assigned a certain permission (per project). You can also add more details about a certain user's involvement in a project. For example, when they started in a project.
If you want to learn more, you can have a read here.
I am upgrading a large Django-based app from Django 1.7 app to Django 2.2 and am having a lot of trouble with a permissions-related mixin.
class PrincipalRoleRelation(models.Model):
"""A role given to a principal (user or group). If a content object is
given this is a local role, i.e. the principal has this role only for this
content object. Otherwise it is a global role, i.e. the principal has
this role generally.
user
A user instance. Either a user xor a group needs to be given.
group
A group instance. Either a user xor a group needs to be given.
role
The role which is given to the principal for content.
content
The content object which gets the local role (optional).
"""
:::
user = models.ForeignKey(User, verbose_name=_(u"User"), blank=True, null=True, on_delete=models.SET_NULL)
group = models.ForeignKey(Group, verbose_name=_(u"Group"), blank=True, null=True, on_delete=models.SET_NULL)
role = models.ForeignKey(Role, verbose_name=_(u"Role"), on_delete=models.CASCADE)
:::
However, this fails to load during app initialization because User, Group, and Role etc are also apps whose loading is in progress and "populate() is not re-entrant" (so Dango complains)
I tried to work round this by amending the above code to create a sort of "skeleton" class which does not attempt to reference any other apps, e.g. :
app_models_loaded = True
try:
from django.contrib.auth import get_user_model
User = get_user_model()
except:
app_models_loaded = False
if app_models_loaded:
from django.contrib.auth.models import Group
user = models.ForeignKey(User, verbose_name=_(u"User"), blank=True, null=True, on_delete=models.SET_NULL)
group = models.ForeignKey(Group, verbose_name=_(u"Group"), blank=True, null=True, on_delete=models.SET_NULL)
role = models.ForeignKey(Role, verbose_name=_(u"Role"), on_delete=models.CASCADE)
:::
Then in manage.py I would define the full mixin class, called say PrincipalRoleRelation2 and overwrite the skeleton class via the code :
from django.contrib import admin
from permissions.models import PrincipalRoleRelation
if admin.site.is_registered(PrincipalRoleRelation):
admin.site.unregister(PrincipalRoleRelation)
admin.site.register(PrincipalRoleRelation, PrincipalRoleRelation2)
However, although this almost seems to work, I am not seeing some of the PrincipalRoleRelation2 attributes, "role" for example, in what I hoped would be the re-mapped PrincipalRoleRelation class with all attributes present.
I feel I am digging myself into an ever deeper hole, and that the above approach is unsound and will never work properly. So any help would be very much appreciated!
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
edit: In response to schillingt's comment, the User class is defined as follows:
class User(AbstractBaseUser): # , PermissionsMixin):
""" Custom user model
Currently just used by the tests for django-permissions
All unique user fields required for a user
NB: Fields that are customizable across multiple identities will be part of a Profile object
"""
# Dont use PermissionsMixin since not using contrib.auth.models.Permissions
# and not using authentication backend perms ... so its only relevant for groups
# ... however it causes user.groups relations name clashes ..
# But we are using the groups part with django-permissions:
groups = models.ManyToManyField(Group, verbose_name=_('groups'),
blank=True, help_text=_('The groups this user belongs to. A user will '
'get all permissions granted to each of '
'his/her group.'),
related_name="user_set", related_query_name="user")
is_superuser = models.BooleanField(_('superuser status'), default=False,
help_text=_('Designates that this user has all permissions without '
'explicitly assigning them.'))
username = models.EmailField(_('Email (Username)'), max_length=255, unique=True)
# Make username an email and just dummy in email here so its clearer for user.email use cases
As a solution for circular refferrence, django has an ability to specify ForeignKey (or any other relational field) with string refferrence to related model, instead of importing the actual class.
user = models.ForeignKey('users.User', on_delete=models.CASCADE)
This is imo the recommended way to define related fields.
I am just learning Django so I thought of creating a project called job board to understand more in detail. I have drawn the following use case.
People can register as job seekers, build their profiles and look for
jobs matching their skillsets
Companies can register, post jobs.
Multiple representatives from a company should be able to register
and post jobs.
Independent Recruiter can create an account as well.
The company can contact to that independent recruiter.
How would be the model design for such a use case? I am confused with the multiple user types in Django. Some favors creating a user profile, while some favors using Groups.
For now, I could only do the following
class User(AbstractUser):
'''
Abstract user because django recommends to start with custom user
'''
username = None
email = models.EmailField(_("Email Address"), unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
objects = UserManager()
def __str__(self):
return self.email
class Company(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
I could create a model for User and Company. But I have no idea on handling multiple user types like user can be either job seeker or recruiter. Also, multiple representatives from a company should be able to register and post jobs as well as there can be independent recruiter as well. How would you handle such a case if you have to? Can anyone help me in a step by step guide, please? This way it will clear my confusion and will help me in better design of tables in the future.
Update with example in a nutshell
class User(models.Model):
'''
User can be of any 3 types or can have multiple role as well
'''
is_job_seeker = models.BooleanField(default=False)
is_recruiter = models.BooleanField(default=False)
is_mentor = models.BooleanField(default=False)
class Company(models.Model):
user = models.ForeignKey(User) # only user with is_recruiter flag active can be
class JobSeeker(models.Model):
user = models.OneToOneField(User)
# job seeker profile related fields like experiences, skills, education, profile image etc
class Recruiter(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
company = models.ForeignKey(Company, null=True, blank=True)
# recruiter related profile
Your implementation is almost there. It doesn't look like you need a custom user model right now, so I would just use Django's default.
I would have something like:
from django.conf import settings
from django.db import models
class Company(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
# Other company-related fields
class JobSeeker(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
# Other jobseeker-related fields
class Recruiter(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
company = models.ForeignKey(Company, null=True, blank=True)
# Other recruiter-related fields
When you create any of the models above, you can assign them a user account; and for recruiter, you can assign the company they work for. For example, a company named stack_overflow can have its own company account with a username/password/etc. Then, recruiters who work for stack_overflow could also have their own accounts with their own username/password/etc. Running a command like stackoverflow.recruiter_set will give you all recruiters who work for stack_overflow.
Note that I do not reference User directly. Using the above approach makes your life easier if you decide to switch User models in the future.
I am assuming you don't want to create a User, then create a Company and link it to that user - you just want to do it in one go. That's a slightly different question and the solution will involve you creating a sign-up Form, or something of that sort, where you can add some logic about whether the user is a company, recruiter or jobseeker.
Regarding your other points, it looks like you're looking to set user permissions. Here are the docs for setting default permissions for your custom users, and here are the general docs for Django's built-in permissions system. For example, your Company and Recruiter model could return True for has_perm('your_app.add_job'), while your Jobseeker model returns False. I.e. Companies and Recruiters can create Jobs, but jobseekers cant.
Hope this helps!
I'm using a 'Task' Model to create operations/administrative tasks in my dashboard. Each task has an assignee, and a reviewer. The assignee completes the task by passing several checks, and the reviewer verifies their work, both of these require each user to edit a check, but neither user should be able to access or modify the other's result.
If the assignee views the Task (with checks inline), they should only be able to modify the "result" and "comment" elements of the check, where as the reviewer can only edit the "review_result" and "reviewer_comment" elements.
To validate this I need to use the fact that given a check, the current user editing the page is equal to check.task.assignee or check.task.reviewer.
I cannot find a simple way to do this, even using django-guardian, as this requires field-level permissions, rather than object level. I considered using modelForm validation, but cannot find a way to access the user from within the model with some hacks such as django-cuser.
Is there another architecture which would allow this? The only way forward that I can see is to use django-guardian combined with two checks, a check and a checkReview, and set object level permissions as the assignee and reviewer are chosen.
class Task(PolymorphicModel):
date_created = models.DateTimeField(auto_created=True)
date_accepted = models.DateTimeField(null=True)
date_reviewed = models.DateTimeField(null=True)
date_closed = models.DateTimeField(null=True)
state = FSMField(default="open")
assignee = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="assigned_tasks",
related_query_name="assigned_task",
)
reviewer = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
null=True,
related_name="review_tasks",
related_query_name="review_task",
)
class Check(PolymorphicModel):
result = models.BooleanField(null=True)
comment = models.CharField(max_length=500, null=True, blank=True)
review_result = models.BooleanField(null=True)
reviewer_comment = models.CharField(max_length=500, null=True)
task = models.ForeignKey(Task, on_delete=models.CASCADE)
The correct method to achieve this is to override the get_readonly_fields method of InlineModelAdmin (In your Inline class).
def get_readonly_fields(self, request, obj=None):
if obj is None:
logger.error("An admin has created a check from the dashboard (this should not happen)!")
return []
user = request.user
fields = [field.name for field in self.opts.local_fields]
if user == obj.assignee:
fields.remove(['result', 'comment'])
elif user == obj.reviewer:
fields.remove(['review_result', 'reviewer_comment'])
return fields
I've been trying to learn django for the past couple of days, and I've made a basic bug tracking software as of now (using the admin part of django)
Ive created a table called bugs:
from django.db import models
# Create your models here.
class Bugs(models.Model):
STATUS_CHOICE = (
('Work In Progress', 'Work In Progress'),
('Incomplete', 'Incomplete'),
('Completed', 'Completed')
)
Project_Name = models.CharField(max_length=100)
Basic_Description = models.CharField(max_length=100)
Detailed_Description = models.TextField(default='The Description, here.')
Status = models.CharField(max_length=18, choices=STATUS_CHOICE)
Assigned_to = models.CharField(max_length=100)
Reported_by = models.CharField(max_length=50, blank=True, null=True)
Reporters_Mail_ID = models.CharField(max_length=50, blank=True, null=True)
Reported_Date = models.DateTimeField(null=True, blank=True)
Created = models.DateTimeField(auto_now_add=True, null=True, blank=True)
Updated = models.DateTimeField(auto_now=True, null=True, blank=True)
Deadline_Date = models.DateTimeField(null=True, blank=True)
image = models.FileField(null=True, blank=True)
def __str__(self):
return self.Project_Name + ' [' + self.Status + '] ' + self.Basic_Description + ' [' + self.Assigned_to + ']'
class Meta:
verbose_name_plural = "Bugs"
How do I make it so that when a project name is selected, the Assigned_to is also automatically selected?
And how do I make sure that once it is assigned to the person/ Status is edited/ or any sort of edit is made by the SuperUser, a mail is sent to the person it is assigned to, and when an edit is made by the Person it is assigned to, the super user gets a mail saying an edit has been made?
Not that important but, I was also wondering if there is any way to make sure that when the Super user Assigns some work to one of the admins, Only the admin specified can view the Issue??
UPDATE:
Let me try to be more descriptive this time..
So i have 2 types of users:
1st is the Super User who decides what bugs should be placed into the list and also manages the other users as well.
2nd is the other normal users, selected by the superuser (Users can only edit the posts added by the superuser)
The database has 12 columns out of which Project_Name and Assigned_To are 2.
My question is,
How do I make it such that when the superuser enters the Project_Name, the Assigned_To Column is already filled?
take this as an example
Project_Name: Project1 ---> Assigned_To: Person1
Project_Name: Project2 ---> Assigned_To: Person2
Project_Name: Project3 ---> Assigned_To: Person3
Project_Name: Project4 ---> Assigned_To: Person4
Now if the Super User Decides that the bug is in Project 1, and Project 1 is handled by Person 1, How do i make the program such that If Project 1 is chosen, Person 1 will automatically be selected in the Assigned_To Column?
And Once the SuperUser Selects the SAVE option(taking the same case as above). How do I make the program send a mail to Person1 when ever SuperUser Updates the database? And how do I make the program send a mail to the SuperUser when the Person1 Makes changes to the Database?
Well I'm not sure what your questions really are, but as I understood, you first need to add another field for user as following :
from django.contrib.auth import get_user_model
user = models.ForeignKey(get_user_model(), blank=True, null=True)
regarding sending email when an object of the model is saved or updated, you should override the save() method of the model and put the code you want to execute.
You can use django signals,
When the SuperUser saves or edits. You can write your code to achieve whatever you want.
from django.db.models.signals import post_save
def send_email(sender, instance, created, **kwargs):
# your code goes here...
post_save.connect(send_email, sender=Bugs)
ref: https://docs.djangoproject.com/en/2.0/ref/signals/#post-save
Ok, so first off, it's common practice to use all lower case for model fields. Python has a style guide called PEP8 which documents all the best practices for writing python. So take a look here to get started; https://www.python.org/dev/peps/pep-0008/#descriptive-naming-styles
Now if your assigned_to model should reference a user for the model to relate to you should indeed do as Milad said & use a ForeignKey to the User model because there are many benefits to using relationships where it makes sense for your data.
If you want the assigned_to to be automatically filled based on the project_name then you'd have to use javascript. You'd probably send an AJAX request on selection of the project to go to the backend with the project value. Then send back the assignee for that project which would be used in the javascript to set the value of the assigned_to field.
As far as sending emails go when changes are made to your model, you should use signals in python. Signals get fired when changes are made to objects, for example;
from django.core.mail import send_mail
from django.db.models.signals import post_save
from django.dispatch import receiver
from bugs.models import Bugs
#receiver(post_save, sender=Bugs)
def bugs_post_save(sender, instance=None, created=None, update_fields=None, **kwargs):
if created or 'assigned_to' in update_fields:
# We have a new object or the assignee has changed
send_mail(
'Subject here',
'Here is the message.',
'from#example.com',
['to#example.com'],
fail_silently=False,
)
The docs on email sending are here
Your receiver functions like this can be included underneath your models or in a specific signals.py module which you'd import in the application config. That would like something like;
# bugs/apps.py
from django.apps import AppConfig
class BugsConfig(AppConfig):
name = 'bugs'
verbose_name = "Bugs"
def ready(self):
# importing signals
import signals