Django manipulate model fields programmatically - python

I want to use the following base model to allow our team to add a unique_reference field to various models where required, using custom attributes (namely prefix, length and the field_name of the field).
is there a clean and agreeable way to achieve what I am looking for with Django?
class UniqueFieldModel(models.Model):
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
self.prefix = getattr(self.Meta, 'unique_reference_prefix')
self.unique_ref_length = getattr(self.Meta, 'unique_reference_field_name',
settings.UNIQUE_REFERENCE_MAX_LENGTH)
unique_ref_field_name = getattr(self.Meta, 'unique_reference_field_name')
unique_reference_field = models.CharField(
max_length=self.unique_ref_length,
blank=False,
null=False
)
setattr(self, unique_ref_field_name, unique_reference_field)
super(UniqueFieldModel, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
if not self.unique_reference:
prefix = getattr(self.Meta, 'unique_reference_prefix')
self.unique_reference = \
self.gen_unique_value(
prefix,
lambda x: get_random_string(x),
lambda x: self.objects.filter(unique_reference=x).count(),
self.unique_ref_length
).upper()
I would say that one possible way would be to normalise the name of the unique_reference field over all models.
But it is suboptimal, as it will require a lot of refactoring, and will be semantically incorrect in some cases.

Related

Add operation to save method of abstract save method of parent model in Django

I create base model and inherit that in all of my models. This is my BaseModel:
class BaseModel(models.Model):
create_date = models.DateTimeField(auto_now_add=True)
update_date = models.DateTimeField(auto_now=True)
created_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='created_%(class)ss')
updated_by = models.ForeignKey('UserManager.User', default=1, on_delete=models.SET_DEFAULT,related_name='updated_%(class)ss')
class Meta:
abstract = True
ordering = ['create_date']
def save(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
if self.user:
if self.user.pk is None:
self.created_by = self.user
self.updated_by = self.user
super(BaseModel, self).save(*args, **kwargs)
Now, I want to add some operations to save method of one of child models like this:
class Child(BaseModel):
# Some fields go here.
def save(self, *args, **kwargs):
# Some operations must run here.
But save method of child model does n't run anymore!
How can I use save method of child model with save method of abastract=True model?
If you inherit ChildModel from BaseModel, when you get to the save method in BaseModel 'self.class' is still ChildModel. So it finds the super of Child, which is BaseModel, so calls the save in BaseModel.
So just call ,
super(ChildModel, self).save(*args, **kwargs)

Django Foreign Key Reverse Filtering

I am still relatively new to Django and still struggle somewhat with ForeignKey filtering and I'd appreciate any help with my problem. I have 2 models below and in my PositionUpdateForm I need the 'candidate' field choices to be only the applicants to that position.
class Position(models.Model):
title = models.CharField(max_length=128)
candidate = models.ForeignKey('careers.Applicant',
on_delete=models.SET_NULL,
related_name='candidates',
blank=True,
null=True
)
class Applicant(models.Model):
first_name = models.CharField(max_length=128)
blank=False,
)
position = models.ManyToManyField(Position,
related_name='applicants',
blank=True
)
In my form I was trying each of the following:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.filter(???))
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = Applicant.objects.filter(???)
Thank you for any assistance.
If you want to have the Applicants that have a position to that Position, you can obtain that with:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.empty())
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = Applicant.objects.filter(position=self.instance)
or we can use the relation in reverse:
class PositionUpdateForm(forms.ModelForm):
candidate = forms.ModelChoiceField(queryset=Applicant.objects.empty())
def __init__(self, *args, **kwargs):
super(PositionUpdateForm, self).__init__(*args, **kwargs)
self.fields['candidate'].queryset = self.instance.applicants.all()
Note that you can only use this when you update a Position model, since otherwise there are no related Applicant records of course.

How to implement Singleton in Django

I have an object that need to be instantiated ONLY ONCE. Tried using redis for caching the instance failed with error cache.set("some_key", singles, timeout=60*60*24*30) but got serialization error, due the other thread operations:
TypeError: can't pickle _thread.lock objects
But, I can comfortably cache others instances as need.
Thus I am looking for a way to create a Singleton object, I also tried:
class SingletonModel(models.Model):
class Meta:
abstract = True
def save(self, *args, **kwargs):
# self.pk = 1
super(SingletonModel, self).save(*args, **kwargs)
# if self.can_cache:
# self.set_cache()
def delete(self, *args, **kwargs):
pass
class Singleton(SingletonModel):
singles = []
#classmethod
def setSingles(cls, singles):
cls.singles = singles
#classmethod
def loadSingles(cls):
sins = cls.singles
log.warning("*****Found: {} singles".format(len(sins)))
if len(sins) == 0:
sins = cls.doSomeLongOperation()
cls.setSingles(sins)
return sins
In the view.py I call on Singleton.loadSingles() but I notice that I get
Found: 0 singles
after 2-3 requests. Please what is the best way to create Singleton on Djnago without using third party library that might try serialising and persisting the object (which is NOT possible in my case)
I found it easier to use a unique index to accomplish this
class SingletonModel(models.Model):
_singleton = models.BooleanField(default=True, editable=False, unique=True)
class Meta:
abstract = True
This is my Singleton Abstract Model.
class SingletonModel(models.Model):
"""Singleton Django Model"""
class Meta:
abstract = True
def save(self, *args, **kwargs):
"""
Save object to the database. Removes all other entries if there
are any.
"""
self.__class__.objects.exclude(id=self.id).delete()
super(SingletonModel, self).save(*args, **kwargs)
#classmethod
def load(cls):
"""
Load object from the database. Failing that, create a new empty
(default) instance of the object and return it (without saving it
to the database).
"""
try:
return cls.objects.get()
except cls.DoesNotExist:
return cls()
The code below simply prevents the creation of a new instance of the Revenue model if one exists. I believe this should point you in the right direction.
Best of luck !!!
class RevenueWallet(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
class Meta:
verbose_name = "Revenue"
def save(self, *args, **kwargs):
"""
:param args:
:param kwargs:
:return:
"""
# Checking if pk exists so that updates can be saved
if not RevenueWallet.objects.filter(pk=self.pk).exists() and RevenueWallet.objects.exists():
raise ValidationError('There can be only one instance of this model')
return super(RevenueWallet, self).save(*args, **kwargs)

Django multiple similar models

I want refactor some of my code in models because it's a little mess. I have couple models.
class Part(models.Model):
class Category(models.Model):
class Labor(models.Model):
And so on, seven in total. I am generating for them ID. For Part it is:
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
And it's pretty similar for rest of classes. Only name of class is changing, three letters identification and paramtere in order_by. I was wondering how can I do it DRY. Because it's 7 lines of code on each class that should be somehow shortened.
I was wondering maybe create BaseModel class inherited from it and somehow change only mentioned things. I would like to get some directions how can I do it better.
Edit:
class Part(models.Model):
par_id = models.CharField(primary_key=True, unique=True, max_length=9, blank=False)
par_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Part.objects.count():
latest = 'XXX00000'
else:
latest = Part.objects.all().order_by('-par_id')[0].par_id
self.par_id = "PAR" + str(int(latest[3:]) + 1).zfill(5)
super(Part, self).save(*args, **kwargs)
class Category(models.Model):
cat_id = models.CharField(primary_key=True, unique=True, max_length=9)
cat_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
if not Category.objects.count():
latest = 'XXX00000'
else:
latest = Category.objects.all().order_by('-cat_id')[0].cat_id
self.cat_id = "CAT" + str(int(latest[3:]) + 1).zfill(5)
super(Category, self).save(*args, **kwargs)
That are two o my classes.
Inheriting is definitely a good idea.
You're not giving much information about the models. So there are 2 main options for inheriting models:
A) To use an AbstractModel which would hold the common fields and some common methods. And then use child models to extend the fields and methods as you need. Here is an example from the django docs:
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)
B) If you're only interested in inheriting or extending the behavioural parts of your models (like the different methods for generating the id's), a proxy model would be a better option. Take a look at the docs: https://docs.djangoproject.com/en/dev/topics/db/models/#proxy-models
Here is an example taken from the django docs:
from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
class MyPerson(Person):
class Meta:
proxy = True
def do_something(self):
# ...
pass
create class BaseModel(models.Model): and copypaste your save method there, but replace Part with self.__class__ , for example
class BaseModel(models.Model):
# some fields here
class Meta:
abstract = True
def save(self, *args, **kwargs):
first_declared_field = self.__class__._meta.fields[1].name
if self.__class__.objects.count():
latest = getattr(self.__class__.objects.order_by('-' + first_declared_field)[0], first_declared_field)
else:
latest = 'XXX00000'
field_value = first_declared_field.name.split('_')[0].upper() + str(int(latest[3:]) + 1).zfill(5)
setattr(self, first_declared_field, field_value)
super(BaseModel, self).save(*args, **kwargs)
class SomeChildModel(BaseModel):
pass

Returning a custom model instance from a manager

I have a model that looks like this and stores data as key-value pairs.
class Setting(models.Model):
company = models.ForeignKey(
Company
)
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
I have a custom Manager on this Model which overrides the get method. When the queries my Model like Settings.objects.get(company=1), I use my over-ridden get method to execute a self.objects.filter(company=1) which returns a list of objects. Can I generate one single custom QuerySet which has all the key-value pairs as fields.
Example:
If the data in my Model was like this:
company name value
------- ---- -----
1 theme custom
1 mode fast
1 color green
I'd like to return a query set that would be pivoted like so when someone executed Settings.objects.get(company=1):
company theme mode color
------ ----- ---- -----
1 custom fast green
I've tried to be verbose but do let me know if I should explain better. I'm not sure if the Django Models allow this scenario.
Thank you everyone.
Edit: Using Proxy models
Is this something I could accomplish using Proxy Models i.e. having a base model to store the key value fields and custom proxy model with normal get and save method?
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.

Categories