Multiple models with similar fields Django - django

Say I have 2 models:
User
Customer
They both have the following SHARED fields:
First name
Last name
Pin code
Id
They also have a shared save() method:
def save(self, *args, **kwargs):
if not self.pk:
id = secrets.token_urlsafe(8)
while User.objects.filter(id=id).count() != 0:
id = secrets.token_urlsafe(8)
self.id = id
super(User, self).save(*args, **kwargs)
How could I create a Base model that they can extend so that I don't need to define all of these things twice? Thanks!!

You can make an abstract base class [Django-doc] that implements the common logic, and then inherit:
class MyBaseClass(models.Model):
id = models.CharField(max_length=8, primary_key=True)
first_name = models.CharField(max_length=128)
last_name = models.CharField(max_length=128)
pin_code = models.CharField(max_length=128)
def save(self, *args, **kwargs):
model = self._meta.model
if not self.pk:
id = secrets.token_urlsafe(8)
while model.objects.filter(id=id).exists():
id = secrets.token_urlsafe(8)
self.id = id
super().save(*args, **kwargs)
class Meta:
abstract = True
class User(MyBaseClass):
pass
class Customer(MyBaseClass):
pass
An abstract class will thus not construct a table, it basically is used to inherit fields, methods, etc. to avoid rewriting the same logic twice.

Related

Queryset Many to many 'ManyRelatedManager' object is not iterable

I'm using Forms to filter the choices of a many to many relationship to only show the ones following a queryset. This is how my Form looks like
class ContractAdminForm(forms.ModelForm):
class Meta:
model = Contract
fields = '__all__'
def __init__(self, *args, **kwargs):
super(ContractAdminForm, self).__init__(*args, **kwargs)
self.fields['client_year_periods'].queryset =ClientYearPeriod.objects.filter(
Q(contract_id__isnull=True) | Q(contract_id=self.instance.id) &
Q(client__in=self.instance.client_entity.all()))
Error: 'ManyRelatedManager' object is not iterable
The issue is being caused by
Q(client__in=self.instance.client_entity))
I need to filter the model years using the client legal model that is connected to Client Ops.
See here how is it built
Models
class ClientEntity(models.Model):
name = models.CharField(max_length=350, verbose_name='Client Name')
countries = models.ManyToManyField(Country, blank=True)
operations_code = models.ManyToManyField(Client)
class ClientYearPeriod(models.Model):
client = models.ForeignKey(Client, on_delete=models.PROTECT, null=True)
[...]
class Contract (models.Model):
client_entity= models.ManyToManyField(ClientLegal)
client_year_periods = models.ManyToManyField(ClientYearPeriod, blank=True)
[...]
class Client(models.Model):
code = models.CharField(max_length=3, verbose_name='Client Code')
name = models.CharField(max_length=250, unique=True)
Expected Result
This contract has selected the client Someone
In the client Entity model it is related to Client = BEM
Here it should only show the ones Client: BEM Once it is saved (not when creating the contract as the client selected above it's not saved yet)
The client__in=… expects a QuerySet, self.instance.client_entity is a manager, not a QuerySet.
But you need to make a QuerySet of Clients, so:
class ContractAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['client_year_periods'].queryset = ClientYearPeriod.objects.filter(
Q(contract_id__isnull=True) |
Q(contract_id=self.instance.id) &
Q(client__in=Client.objects.filter(cliententity__contract=self.instance).distinct())
)
class Meta:
model = Contract
fields = '__all__'

Django ModelChoiceField shows Customers objects(1) etc on list, how do i get it to show name of customers?

models.py
class Customers(models.Model):
Name = models.CharField(max_length=100)
class Staff(models.Model):
Name = models.CharField(max_length=100)
class Cereals(models.Model):
Name = models.CharField(max_length=100)
Operator = models.CharField(max_length=100)
forms.py
class EditCereals(forms.ModelForm):
class Meta:
model = Cereals
fields = ['Name', 'Operator']
widgets = {
'Operator': Select(),
'Name': Select(),
}
def __init__(self, *args, **kwargs):
super(EditCereals, self).__init__(*args, **kwargs)
self.fields['Name'] = forms.ModelChoiceField(queryset=Customers.objects.all().order_by('Name'))
self.fields['Operator'] = forms.ModelChoiceField(queryset=Staff.objects.all().order_by('Name'))
When i run the form 'Name' shows Customers Objects (1), Customers objects (2), etc and same with 'Operator' it shows Staff Objects (1), Staff Objects (2), etc
How do i get it to show the actual names, eg Bill, Fred,
You should use def __str__()... method. This method works for the string representation of any object.
Example
class Customers(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
And please name you instance variable with small letters with underscore. Captial cases should be used for classes only.

Set a field value within form __init__ function

I am trying to find out an efficient way to set a field value within form init method. My models are similar to below
class Users(models.Model):
firstname = models.CharField()
lastname = models.CharField()
class profile(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
class logindetails(models.model):
user = models.ForeignKey(Users, on_delete=models.PROTECT)
profile = models.ForeignKey(profile, on_delete=models.PROTECT)
login_time = models.DateField(auto_now=True)
My form is like as below:
class LoginForm(forms.ModelForm):
class Meta:
model = logindetails
fields = [__all__]
def __init__(self, *args, **kwargs):
self._rowid = kwargs.pop('rowid', None)
super(LoginForm, self).__init__(*args, **kwargs)
instance = profile.objects.get(id=self._rowid)
self.fields['user'] = instance.user <--- Facing difficulties here
Any help will be appreciated.
Django had built-in ways of setting initial form values, the documentation is available here: https://docs.djangoproject.com/en/3.0/ref/forms/api/#dynamic-initial-values

How to create a save method in an abstract model that checks whether an instance exists?

I have the following models:
class PlaceMixin(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
sublocality = models.CharField(max_length=100, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
class Meta:
abstract = True
class Bar(PlaceMixin):
pass
class Restaurant(PlaceMixin):
pass
Bar and Restaurant have almost same save() method:
def save(self, *args, **kwargs):
try:
bar = Bar.objects.get(address=self.address)
except Bar.DoesNotExist:
Do something
super().save(*args, **kwargs)
def save(self, *args, **kwargs):
try:
restaurant = Restaurant.objects.get(address=self.address)
except Restaurant.DoesNotExist:
Do something
super().save(*args, **kwargs)
I was wondering if I can put the method in the Abstract model and pass it to the two inherited model?
def save(self, *args, **kwargs):
try:
temp = self.objects.get(address=self.address)
except self.DoesNotExist:
Do something
super().save(*args, **kwargs)
Something like this? But you can query in an abstract model. I basically need to check if an instance exists for executing an action.
You can make a common save method for both Restaurant and Bar model in a Mixin class like this:
from django.apps import apps
class CommonMixin(object):
def save(self, *args, **kwargs):
if self.__class__.__name__ == "Resturant":
model = apps.get_model('app_name', 'Bar')
if model.objects.filter(address=self.address).exists():
...
else:
model = apps.get_model('app_name', 'Restaurant')
if model.objects.filter(address=self.address).exists():
...
super(CommonMixin, self).save(*args, **kwargs)
And import it in both Restaurant and Bar class:
class Restaurant(CommonMixin, PlaceMixin):
...
class Bar(CommonMixin, PlaceMixin):
...
Probably a better approach is to use a separate model for Address information. Then you won't need a new Mixin to override save(the approach given above feels like over engineering). So lets say you have a different address model, there you can simply put unique=True to restrict duplicate entries:
class Address(models.Model):
address = models.CharField(max_length=255, unique=True)
class PlaceMixin(models.Model):
address = models.ForeignKey(Address)
...
You can use abstract metadata to achieve this. And if you want to use any variable inside class model, you just need to use self.__class__ like so:
class PlaceMixin(models.Model):
name = models.CharField(max_length=200, null=True, blank=True)
address = models.CharField(max_length=200, null=True, blank=True)
sublocality = models.CharField(max_length=100, null=True, blank=True)
city = models.CharField(max_length=100, null=True, blank=True)
class Meta:
abstract = True
def save(self, *args, **kwargs):
try:
self.__class__.objects.get(address=self.address)
except self.__class__.DoesNotExist:
# Do something
else:
super().save(*args, **kwargs)
class Bar(PlaceMixin):
pass
class Restaurant(PlaceMixin):
pass
There are a lot of code design like this in Django source code, a lot of good practices in their project so give it a try. E.g: a line of code on Django repo

Django model audit mixin

Hello I wanted to know how to create a few fields and convert them into a mixin.
Let's say I have the following.
class Supplier(models.Model):
name = models.CharField(max_length=128)
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
I want to create a mixin to avoid writing every time the last 4 fields into different models.
Here is the mixin I would create:
class AuditMixin(models.Model):
created_by = models.ForeignKey(get_user_model(), related_name='%(class)s_created_by')
modified_by = models.ForeignKey(get_user_model(), related_name='%(class)s_modified_by')
created_date = models.DateTimeField(editable=False)
modified_date = models.DateTimeField()
def save(self, *args, **kwargs):
if not self.id:
self.created_date = timezone.now()
self.modified_date = timezone.now()
return super(Supplier, self).save(*args, **kwargs)
class Supplier(AuditMixin):
name = models.Charfield(max_length=128)
How can I make sure that the related_name is relevant to the class the mixin is included into? Also in the save function, How can I make sure the class the mixin is included into is returned (as per the last line)?
Thank you!
Firstly, in any super call, you must always use the current class. So it will always be super(AuditMixin, self)... and your question does not apply.
Django itself takes care of substituting the current class name in related_name if you use the %(class)s syntax, which you have, so again there is nothing else for you to do. See the model inheritance docs.