How to add #property to the base Group model in Django? - python-3.x

I want to be able to add a property to the group model
This is what I have been trying
from django.contrib.auth.models import Group
class GroupExtensions(Group):
class Meta:
proxy = True
#property
def assigned_property(self):
return 'something'
I want to get this property from the base group model but somehow this doesn't work
group_instance.assigned_property
Where group_instance is an instance of the Group model

This behaviour is explained here. So the only way for you to be able to use assigned_property is to query the groups through GroupExtensions. But there is a way to make this work, although you should be careful about doing this:
group_instance.__class__ = GroupExtensions
print(group_instance.assigned_property)

Related

Is there a way to override ClockedSchedule model from Django Celery Beat?

I want to add unique=True attribute to clocked_time field of ClockedSchedule model. Current Scenario is, when multiple threads try to get_or_create schedule, it creates more than one of similar records given schedule is not found, and when next time some thread tries to get the schedule it throws MultipleObjectsReturned exception. So, I was thinking adding a DB constraint might work here. Attaching the code for reference:
schedule, created = ClockedSchedule.objects.get_or_create(**clocked_options)
return schedule
And the model looks like:
class ClockedSchedule(models.Model):
"""clocked schedule."""
clocked_time = models.DateTimeField(
verbose_name=_('Clock Time'),
help_text=_('Run the task at clocked time'),
)
class Meta:
"""Table information."""
verbose_name = _('clocked')
verbose_name_plural = _('clocked')
ordering = ['clocked_time']
def __str__(self):
return '{}'.format(self.clocked_time)
Let me know your thoughts, thanks!

Can you make sure only one object related to another object has a certain field set?

I have a model called Video, and it has related objects on another model called Label. Example here:
class Video(models.Model):
pass
class Label(models.Model):
video = models.ForeignKey(Video, related_name="labels")
current = models.NullBooleanField()
I need to be able to find the current label on a video by doing something like my_video.labels.filter(current=True), and this query should only ever return one label, so only one label on the video should have that field set to True.
Is there a way of ensuring this on the model/db?
Thanks
EDIT: The answer given below has achieved exactly this. Adding some django tests below for anyone else reading as some proof:
class TestLabelIntegrity(TestCase):
def test_a_video_can_have_only_one_current_label(self):
video = Video.objects.create()
label_1 = Label.objects.create(
video=video,
current=True
)
with self.assertRaises(IntegrityError):
label_2 = Label.objects.create(
video=video,
current=True
)
def test_two_different_videos_can_each_have_current_layers(self):
""" No assertions needed, just need to make sure no integrity errors are raised"""
video_1 = Video.objects.create()
label_1 = Label.objects.create(
video=video_1,
current=True
)
video_2 = Video.objects.create()
label_2 = Label.objects.create(
video=video_2,
current=True
)
I believe you can solve this using UniqueConstraint.
Using this, you can restrict that a Video only have a single label that current == True
You can define the UniqueConstraint in the models Meta.
You’ll get a database integrity error on save() if the condition fails.
See the documentation for this here:
https://docs.djangoproject.com/en/4.0/ref/models/constraints/
class Label(models.Model):
...
class Meta:
constraints = [
models.UniqueConstraint(
fields=["current", "video"],
condition=Q(current=True),
name="unique_current_label",
),
]

Django: Abstract base class for managing db_table

I'm trying to build my second django (and python for that matter, my first project being the django tutorial :)) project. since this is supposed to be something real i'd like to be thorough and build a good code structure before i go into the meat of the project.
I have a couple of simple models like this
class Task(models.Model):
name = models.CharField(max_length=50, null=False)
description = models.CharField(max_length=255, null=True)
dueDate = models.DateTimeField()
I'm using PostgreSQL and i set up my models to use the app label as a database schema by defining the meta class of each model like this
class Meta:
managed = True
db_table = 'app_name\".\"modelname'
This works well. But i have to do this for every single model.
I want to keep it DRY though. So what i'm trying to do now is to have an abstract base class that does this automatically
so i tried the following:
class SchemaModel(models.Model):
class Meta():
abstract = True
managed = True
db_table = AppConfig.label+'\".\"'+self.__class__.lower()+'s'
(the base class was then inherited of course and i took the nested Meta class out of the normal models)
this didn't work though because self isn't accessible in Meta
after consulting the documentation i tried this:
class SchemaModel(models.Model):
class Meta():
abstract = True
managed = True
db_table = '%(app_label)\".\"%(class)s'
which lead to the property db_table of every model being "%(app_label)\".\"%(class)s"
>>> t = Task()
>>> t._meta.db_table
'%(app_label)"."%(class)s'
>>>
I didn't find anything similar on the internet. Am i trying to do something impossible or "forbidden"?
Solution
The solution is as shown in elyas answer to set the db_table property at the end of models.py by looping through all __subclasses__()
for model in SchemaModel.__subclasses__():
db_table = '{}s'.format(model._meta.label_lower.replace('.','\".\"'))
model._meta.original_attrs['db_table'] = db_table
model._meta.db_table = db_table
I wouldn't say it's forbidden. But I can't think of any way to do this declaratively. There is a way to do it however.
Firstly, on your existing attempts:
Accessing 'self'
db_table = AppConfig.label+'\".\"'+self.__class__.lower()+'s'
An instance object is never created from the Meta class when models are loaded, and so there is no self to reference. But even if an instance object were created, db_table is an attribute of the class object, so it is evaluated when the class object is created, which is before any instance object is created, so self cannot be accessed when defining a class attribute in this way.
Edit: And as you mentioned, the app_label cannot be accessed through AppConfig.label.
String formatting
db_table = '%(app_label)\".\"%(class)s'
These placeholders are only used in a very specific situation when defining the related_name and related_query_name attributes of ForeignKey or OneToOneField's fields in an abstract base class.
A solution
As I said, I can't think of any declarative way to achieve this. For example, trying to use __qualname__ won't work because you would just end up with SchemaModel.Meta every time.
But you could put a for loop at the bottom of your models.py like this:
for model in SchemaModel.__subclasses__():
# Name your db_table here
db_table = model._meta.app_label + '\".\"' + model._meta.model_name.lower() + 's'
# Set db_table here
model._meta.original_attrs['db_table'] = db_table
model._meta.db_table = db_table
All of SchemaModel's children can be found using the built-in __subclasses__() method.
The db_table attribute needs to be updated in two places. Firstly in _meta, which is (partly) created by copying attributes from the Meta class, and secondly in _meta.original_attrs where the original Meta attributes are stored and are read by Django during migrations.
Alternative solution
Personally I would define the db_table names manually and simply have a unit test that checks that all models adhere to whatever naming convention I've come up. I prefer this so if another developer eyeballs a model they have never seen before they can get the full picture based on the declarations in the model (and the abstract base class) and don't get confused about an operation modifying them elsewhere.

Proper way to do a robust search in Django models via REST framework

I'm writing a web application (DRF + Vue.js) where frontend should have an ability to narrow down GET request results via different filters.
For example, I have a model like this:
class Human(models.Model):
first_name = models.CharField(_('first name'), max_length=50, null=True, blank=True)
last_name = models.CharField(_('last name'), max_length=50, null=True, blank=True)
birth_date = models.DateField(_('birth date'), blank=True, null=True)
city = models.ForeignKey('City', on_delete=models.SET_NULL, blank=True, null=True)
phone_number = models.ForeignKey('Contact' on_delete=models.SET_NULL, blank=True, null=True)
#property
def full_name(self):
# Last name + First name
return ' '.join(str(x) for x in (self.last_name, self.first_name) if x)
#property
def is_adult(self):
now = timezone.now()
if self.birth_date:
if now.year - self.birth_date.year - \
((now.month, now.day) < (self.birth_date.month, self.birth_date.day)) >= 18:
return True
return False
Now I have simple CRUD ViewSet where I can use a list of search_fields to search by all needed fields (in my case that's birth_date, city, phone_number and full_name/is_adult). But here next problems arise:
Using search_fields I can do a search only by all fields specified in the ViewSet's search_fields (frontend can't search only by city or other distinct fields if it wants to) - other way I'll need to create a separate ViewSet (and separate URL?) for every combination of fields to filter by. Terrific.
So it looks like the correct decision must be capable of filtering by several GET parameters at once. Ideally - with opportunity to choose exact/icontains/etc comparison method on each query.
That sounds like a work for django-filter but I'm not sure yet.
It's impossible to search/filter by full_name or is_adult because they are dynamic model properties, not usual fields.
So it looks like instead of using model properties I need to use separate QuerySets (Manager methods?) that will do the logic of fiddling with model fields and creating the filtered result.
But for now I didn't find a way to choose different QuerySets in a single ViewSet depending on GET parameters (or how to use search by these complex properties together with simple search from problem 1?).
And I have no understanding if it is possible to provide this kind of search by the same URL as a "simple" search - like site.com/api/people/?city=^New&full_name=John%20Doe (perfectly - with opportunity to document query parameters for OpenAPI schema / Swagger)
So maybe someone knows which is the most elegant way to provide a capability of such complex search with Django/DRF? In which direction should I look?
"full_name" alias needs to be part of your queryset.
You can achieve it by queryset annotation.
In your People view (api/people/ controller) you have to set your queryset to be:
from django.db.models.functions import Concat
from django.db.models import Value
queryset = Human.objects.annotate(fullname=Concat('first_name', Value(' '), 'last_name'))
Also, customize your "filter_backed" to act the way you need.
class PeopleFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
params_serializer = PeopleFilterSerializer(data=request.params)
params_serializer.is_valid(raise_exception=True)
valid_params = params_serializer.data
if full_name := valid_params.get("full_name")
queryset = queryset.filter(full_name__icountains=full_name)
# insert here all of other filters logic
return queryset
PeopleFilterSerializer is your custom params serializer,
You have to specify there all of your accepted params:
from rest_framework import serializers
class PeopleFilterSerializer(serializers.Serializer):
full_name = serializers.CharField() # set required=False if not required
city = serializer.CharField()

Can I use ModelSerializer (DRF) to move multiple fields to a JSON field in a CREATE method?

I'm building an API with the Django Rest Framework. The main requirement is that it should allow for the flexible inclusion of extra fields in the call. Based on a POST call, I would like to create a new record in Django, where some fields (varying in name and number) should be added to a JSON field (lead_request).
I doubt if I should use the ModelSerializer, as I don't know how to handle the various fields that should be merged into one field as a JSON. In the create method, I can't merge the additional fields into the JSON, as they aren't validated.
class Leads(models.Model):
campaign_id = models.ForeignKey(Campaigns, on_delete=models.DO_NOTHING)
lead_email = models.EmailField(null=True, blank=True)
lead_request = JSONField(default=dict, null=True, blank=True)
class LeadCreateSerializer(serializers.ModelSerializer):
def get_lead_request(self):
return {key: value for key, value in self.request.items() if key.startswith('rq_')}
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']
def create(self, validated_data):
return Leads.objects.create(**validated_data)
The documentation mostly talks about assigning validated_data, but here that isn't possible.
If I understood correctly and you want to receive parameters through the URL as well, here's an example of how you could achieve what you want:
class LeadViewSet(viewsets.ModelViewSet):
def create(self, request, *args, **kwargs):
data = request.data
lead_request = generate_lead_request(request)
data['lead_request'] = lead_request
serializer = self.get_serializer(data=data)
serializer.is_valid(raise_exception=True)
...
And on generate_lead_request you could parse all the additional fields that may have been sent through request.data (body) as well as through the request.query_params.
If i understand the problem properly main obstruction here is we don't know the exact JSON data format of lead_request. I am thinking about two possible model of solution for this problem. I not sure either of them is appropriate or not. Just want to share my opinion.
case 1
Lets assume data passed to LeadCreateSerializer in this type of format
data = {
'campaign_id': campaign_id,
'lead_email': lead_email,
'lead_request': {
# lead_request
}
}
Then this is easy, normal model serializer should able to do that. If data is not in properly formatted and it possible to organize before passing to serializer that this should those view or functions responsibility to make it proper format.
case 2
Lets assume this is not possible to organize data before passing that in LeadCreateSerializer then we need to get our related value during the validation or get of lead_request. As this serializer responsibility is to create new instance and for that validate fields so we assume in self.context the whole self.context.request is present.
class LeadCreateSerializer(serializers.ModelSerializer):
def generate_lead_request(self, data):
# do your all possible validation and return
# in dict format
def get_lead_request(self):
request = self.context.request
lead_request = self.generate_lead_request(request.data)
return lead_request
class Meta:
model = Leads
fields = ['campaign_id',
'lead_email',
'lead_request']

Resources