Add unique users to ProjectMember model in Django [duplicate] - python-3.x

This question already has answers here:
Django - create a unique database constraint for 2 or more fields together
(4 answers)
Closed 1 year ago.
I'm new to Django. Can anyone out there for the rescue.
In my current proect, I've couple of models inside a Django app: Project, ProjectMember. Here's their model and admin structure.
models.py
class Project(models.Model):
name = models.CharField(max_length=100)
start_date = models.DateField()
end_date = models.DateField()
description = models.TextField(max_length=1000)
class ProjectMember(models.Model):
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
admin.py
from .models import Project, ProjectMember
class ProjectMemberInline(admin.TabularInline):
model = ProjectMember
extra = 0
class ProjectAdmin(admin.ModelAdmin):
list_display = ('name', 'start_date', 'end_date')
inlines = [ProjectMemberInline]
admin.site.register(Project, ProjectAdmin)
What I want to achieve is, every Project(i.e. model) should have unique users(ProjectMember) and every user can be associated with multiple projects. But with the current structure, system allows duplicate ProjectMember insertion in the admin. Please see the screenshot, I've add here 2 same users.
Any help is appreciated.

What you have effectively done is define the "through" table for your own M2M relationship here. You have 2 options:
Redefine your tables to use an out of the box M2M relationship - https://docs.djangoproject.com/en/3.2/topics/db/examples/many_to_many/
Add a unique_together constraint - https://docs.djangoproject.com/en/3.2/ref/models/options/#unique-together
There's nothing wrong with what you've done, it's just a question of whether you want to use what Django gives you, or roll your own, so the solution of choice is up to you.
Edit: As the other answer points out, uniqie_together is deprecated infavour of UniqueConstraint, but the concept is the same, and which one you use may depend on the version of Django you're on. It sounds like this is a new project, so probably a recent version, in which case UniqueConstraint...

We can work with Django's constraint framework such that we can add a UniqueConstraint [Django-doc] over the two ForeignKeys:
class ProjectMember(models.Model):
project = models.ForeignKey(Project, on_delete=models.SET_NULL, null=True)
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['project', 'user'],
name='onceperproject'
)
]
Most modern databases will enforce this, and thus prevent that you can add the same user twice per project, or the same project twice to a user.

Related

How to add custom field to auth_permission model in django?

I am working with auth_permission model and to add extra fields in this model.
class Permission(models.Model):
name = models.CharField(_('name'), max_length=255)
content_type = models.ForeignKey(
ContentType,
models.CASCADE,
verbose_name=_('content type'),
)
codename = models.CharField(_('codename'), max_length=100)
Extra field I wanted to add is
application_id = models.ForeignKey(ApplicationTbl, db_column='application_id', on_delete=models.CASCADE, blank=True, null=False)
Can someone tell the work around to add it to auth_permission model.
To your models.py put this:
from django.contrib.auth.models import Permission
# inject application_id atribute to the Django Permission model
if not hasattr(Permission, 'application_id'):
application_id = models.ForeignKey(ApplicationTbl, db_column='application_id', on_delete=models.CASCADE, blank=True, null=False)
application_id.contribute_to_class(Permission, 'application_id')
After that, run migration and you should be ready to go.
The official standard way in Django docs to do this is to define it in class Meta inside your model; which if it's your user model, you could also consider customizing it by using PermissionsMixin;
which I believe is also applicable in your case if you put everything in its right place.
You have not provided much detail about your system and what you're trying to do but I guess you're trying to figure out in which application context the user is doing his activity. In that case and if your system is of a reasonably big scale, you could also try considering user authentication backend as a separate app folder in your Django project. hope I could help.

What will happen if I migrate django models directly from the mysql database

Earlier i had this as my model class.
class Certificate(models.Model):
User = models.AutoField(primary_key=True)
but after few months i added "data field" into my model class directly from the sql and without using migrations.
Now i am just wondering if i change the code in my models and again make migrations, will it work?
class Certificate(models.Model):
User = models.AutoField(primary_key=True)
data = models.TextField(null=True, blank=True)

Django Python 3.x - OneToOneField override delete() with ContentTypes

Scenario: there is different service types, i.e. clothes_washing, room_cleaning and room_maintenance.
Each one of these services have common fields like service_date for example.
Upon scenario I have model for each service and a Service model with common fields. the relation between Service model and each service model is OneToOneField.
I've tried to override the delete() function in Service model following this answer and It works for me but for only one service (like wheel in self.wheel.delete()). but if I want to delete upon the service type? how to achieve that approach?
my models.py:
class ClothesWashing(models.Model):
# special fields for clothes_washing
service = models.OneToOneField(Service, on_delete=models.DO_NOTHING, null=True)
class RoomCleaning(models.Model):
# special fields for room_cleaning
service = models.OneToOneField(Service, on_delete=models.DO_NOTHING, null=True)
class Service(models.Model):
# common fields for all services
def delete(self, *args, **kwargs):
#here I wanna "roomcleaning" attribute to be dynamic upon content type
self.roomcleaning.delete()
return super(self.__class__, self).delete(*args, **kwargs)
You can set the on_delete parameters to CASCADE:
class ClothesWashing(models.Model):
# special fields for clothes_washing
service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)
class RoomCleaning(models.Model):
# special fields for room_cleaning
service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)
The on_delete=… parameter [Django-doc] specifies what should happen when the item to which it refers is removed. So if Service is removed, and there is a ClothesWashing model that refers to it, then you can specify what to do.
By using CASCADE [Django-doc], you will remove the related ClothesWashing object as well, or as specified in the documentation:
Cascade deletes. Django emulates the behavior of the SQL constraint ON DELETE CASCADE and also deletes the object containing the ForeignKey.
It is better to implement it with this triggers, since methods like .delete() are not always called by the ORM when deleting in bulk. So Service.objects.all().delete() will delete all services, but will never call the .delete() method of your Service. By defining the triggers, you specify to Django what should happen with items that relate to it.
In this specific case, you perhaps might want to work with model inheritance [Django-doc]. Django can implement some boilerplate logic itself (like OneToOneFields to the parent model, etc.).
EDIT: If you want to delete the service if the given ClothesWashing, RoomCleaning, etc. are removed, you can override the .delete() method to delete that one too, you can for example make an abstract base class with:
class ServiceBase(models.Model):
# special fields for clothes_washing
service = models.OneToOneField(Service, on_delete=models.CASCADE, null=True)
def delete(self, *args, **kwargs):
service = self.service
super().delete(*args, **kwargs)
self.service.delete()
class Meta:
abstract = True
class ClothesWashing(ServiceBase):
# …
pass
class RoomCleaning(ServiceBase):
# …
pass
But likely if you use the ORM eventually some objects will not be removed, because of said ways to circumvent this.

Inheriting from both ABC and django.db.models.Model raises metaclass exception

I am trying to implement a Django data model class, which is also an interface class, using Python 3. My reason for doing so is, I'm writing a base class for my colleague, and need him to implement three methods in all of the classes he derives from mine. I am trying to give him a simplified way to use the functionality of a system I've designed. But, he must override a few methods to supply the system with enough information to execute the code in his inherited classes.
I know this is wrong, because it's throwing exceptions, but I'd like to have a class like the following example:
from django.db import models
from abc import ABC, abstractmethod
class AlgorithmTemplate(ABC, models.Model):
name = models.CharField(max_length=32)
#abstractmethod
def data_subscriptions(self):
"""
This method returns a list of topics this class will subscribe to using websockets
NOTE: This method MUST be overriden!
:rtype: list
"""
I understand I could avoid inheriting from the ABC class, but I'd like to use it for reasons I won't bore you with here.
The Problem
After including a class, like the one above, into my project and running python manage.py makemigrations I get the error: TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases. I have searched Stack Overflow, but have only find solutions like the following one:
class M_A(type): pass
class M_B(type): pass
class A(metaclass=M_A): pass
class B(metaclass=M_B): pass
class M_C(M_A, M_B): pass
class C:(A, B, metaclass=M_C): pass
I've read the following posts:
Using ABC, PolymorphicModel, django-models gives metaclass conflict
Resolving metaclass conflicts
And I've tried many variations of those solutions, but I still get the dreaded metaclass exception. Help me Obi-Wan Kenobi, you're my only hope. :-)
I had the same need and found this. I've altered the code for clarity and completeness. Basically you need an extra class which you can use for all your model interfaces.
import abc
from django.db import models
class AbstractModelMeta(abc.ABCMeta, type(models.Model)):
pass
class AbstractModel(models.Model, metaclass=AbstractModelMeta):
# You may have common fields here.
class Meta:
abstract = True
#abc.abstractmethod
def must_implement(self):
pass
class MyModel(AbstractModel):
code = models.CharField("code", max_length=10, unique=True)
class Meta:
app_label = 'my_app'
test = MyModel(code='test')
> TypeError: Can't instantiate abstract class MyModel with abstract methods must_implement
Now you have the best of both worlds.
I found a solution that worked for me, so thought I would post it here in case it helps someone else. I decided to not inherit from the ABC class, and instead just raise an exception in the "abstract" methods (the ones the derived class must implement). I did find helpful information in the Django docs, describing using Django data models as an Abstract base class and also Multi-table inheritance.
Django Data Model as an Abstract Base Class
Quoted from the docs:
Abstract base classes are useful when you want to put some common information into a number of other models. You write your base class and put abstract=True in the Meta class. This model will then not be used to create any database table. Instead, when it is used as a base class for other models, its fields will be added to those of the child class.
An example:
from django.db import models
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
class Student(CommonInfo):
home_group = models.CharField(max_length=5)
The Student model will have three fields: name, age and home_group.
The CommonInfo model cannot be used as a normal Django model, since it
is an abstract base class. It does not generate a database table or
have a manager, and cannot be instantiated or saved directly.
Fields inherited from abstract base classes can be overridden with
another field or value, or be removed with None.
Multi-table Inheritance with a Django Data Model
My understanding of "multi-table inheritance" is, you can define a data model and then also use it as a base class for a second data model. The second data model will inherit all the fields from the 1st model, plus its own fields.
Quoted from the docs:
The second type of model inheritance supported by Django is when each
model in the hierarchy is a model all by itself. Each model
corresponds to its own database table and can be queried and created
individually. The inheritance relationship introduces links between
the child model and each of its parents (via an automatically-created
OneToOneField). For example:
from django.db import models
class Place(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=80)
class Restaurant(Place):
serves_hot_dogs = models.BooleanField(default=False)
serves_pizza = models.BooleanField(default=False)
All of the fields of Place will also be available in Restaurant,
although the data will reside in a different database table. So these
are both possible:
>>> Place.objects.filter(name="Bob's Cafe")
>>> Restaurant.objects.filter(name="Bob's Cafe")

django model add default columns

I have to following model:
class publication_type(models.Model):
name = models.CharField(max_length=200)
description = models.TextField(blank=True,null=True,default=None)
And I would like to add a few "default" entrys to this model, that I can always refer to without assuming whether they exist yet or not, for example:
publication_type.objects.create(id=1,name="article",description="...")
Where would be the best position in the django code to position to put this code?

Resources