DRF serializers how to best handle indirect references to a FK? - python

Let's say I have the following models:
class Thing(models.Model):
thing_key = models.AutoField(primary_key=True)
state = models.ForeignKey(State, db_column='state_key', on_delete=models.CASCADE, default=0)
[...other fields unrelated to the question...]
class Meta:
db_table = 'THING'
class State(models.Model):
state_key = models.AutoField(primary_key=True)
flag = models.IntegerField(unique=True)
description = models.CharField(max_length=50)
class Meta:
db_table = 'STATE'
And let's say I need to create new Things based on data received via POST requests.
And let's say these POST requests do NOT contain the FK of Thing, which is state_key, but actually the flag field from the State model.
What is the best way to implement serializers that help accomplish the following things:
Create a new Thing even though the state_key is unknown.
Return a serialized representation of the newly created Thing without exposing the state_key.
After reading and re-reading the documentation, the best I could do is the following. It works, but I'm suspect there's a much more straightforward way to do it:
class FlagField(serializers.RelatedField):
def to_representation(self, value):
return value.flag
def to_internal_value(self, data):
return State.objects.get(flag=data)
class ThingSerializer(serializers.ModelSerializer):
state_flag = FlagField(queryset=State.objects.all(), source='state')
class Meta:
model = Thing
exclude = ['state']
Is this an acceptable approach? If not, what is wrong with it and how else could I accomplish the goal?
Thanks in advance!

Related

How can I restrict the list of objects in API view once an object has been added to a relationship?

I am working in django-rest-framework and I have three models: Event, Performer, and Link. I have many-to-many relationships established on the Event and Performer models as 'links' pointing to the Link model. In the API view, when I am creating or updating an event or performer, I am given a list of all links. I would like them to be removed as options once they've been associated with another object, but I can't seem to figure out how to. Below is my code:
class Link(models.Model):
created = models.DateTimeField(auto_now_add=True)
address = models.URLField()
def __str__(self):
return f"{self.address}"
class Meta:
ordering = ['created']
class Performer(models.Model):
created = models.DateTimeField(auto_now_add=True)
first_name = models.CharField(max_length=20)
last_name = models.CharField(max_length=20)
links = models.ManyToManyField(Link)
def __str__(self):
return f"{self.first_name} {self.last_name}"
class Meta:
ordering = ['created']
class Event(models.Model):
created = models.DateTimeField(auto_now_add=True)
sale_date = models.DateTimeField()
event_date = models.DateTimeField()
performer = models.ForeignKey(Performer, on_delete=models.CASCADE)
links = models.ManyToManyField(Link)
class Meta:
ordering = ['event_date']
and I'm using this for serializers:
class LinkSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Link
fields = ['url', 'address']
class PerformerSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Performer
fields = ['url', 'first_name', 'last_name', 'links']
class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
fields = ['url', 'performer', 'sale_date', 'event_date', 'links']
I thought about using
ManyToManyField.limit_choices_to
but I don't know what my selector would look like. I also thought I could use
Link.objects.exclude(...)
or
Link.objects.filter(...)
call somewhere but I just don't know where. Thanks to anyone who can help!
Edit: thought I’d add that what I thought would work is to use ‘limit_choices_to’ to filter out any links that are included in a relationship, but I couldn’t figure out how to test if an object was in a relationship (and since there’s multiple relationships only testing for one isn’t perfect either)
You should make use of the Serializer class' get_queryset method:
class LinkSerializer(serializers.HyperlinkedModelSerializer):
def get_queryset(self):
return super().get_queryset().filter(performer=None, event=None)
class Meta:
model = Link
fields = ['url', 'address']
I figured out what I was trying to accomplish with this: I needed to restrict the choices for the field at the model level, which I was able to do by passing a predetermined restriction to the 'limit_choices_to=' parameter. See code below and thank you to #anthony2261 for the suggestion, your filter section helped me to understand how to filter even though it wasn't the type of filtering I needed!
# create a dict of filter conditions(?)
restrict_choices = {'performer': None, 'event': None}
class Performer(...):
...
# refer to the restriction defined previously
# when defining the links relationship.
links = models.ManyToManyField(Link, limit_choices_to=restrict_choices)

Django: How to limit_choices_to when using custom intermediate table

Let me start by saying that I am working with a legacy database so avoiding the custom intermediate table is not an option.
I'm looking for an alternative way to get the limit_choices_to functionality as I need to only present the options flagged by the sample_option boolean in the Sampletype Model in my ModelForm:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
Here is a simplified view of my models
class Planet(models.Model):
name= models.CharField(unique=True, max_length=256)
samples = models.ManyToManyField('Sampletype', through='Sample')
class Sample(models.Model):
planet = models.ForeignKey(Planet, models.DO_NOTHING)
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING)
class Sampletype(models.Model):
name = models.CharField(unique=True, max_length=256)
sample_option = models.BooleanField(default=True)
Sample is the intermediate table.
Normally, if the project had been started with Django in the first place, I could just define the ManyToManyField declaration as:
samples = models.ManyToManyField('Sampletype', limit_choices_to={'sample_option'=True})
But this is not an option.. So how do I get this functionality ?
Django clearly states in their documentation that:
limit_choices_to has no effect when used on a ManyToManyField with a
custom intermediate table specified using the through parameter.
But they offer no information on how to get that limit in place when you DO have a custom intermediate table.
I tried setting the limit_choices_to option on the ForeignKey in the Sample Model like so:
sampletype = models.ForeignKey('Sampletype', models.DO_NOTHING, limit_choices_to={'sample_option': True})
but that had no effect.
Strangely, I find no answer to this on the web and clearly other people must have to do this in their projects so I'm guessing the solution is really simple but I cannot figure it out.
Thanks in advance for any help or suggestions.
You could set the choices in the __init__ method of the form:
class PlanetForm(ModelForm):
class Meta:
model = Planet
fields = ['name', 'samples']
def __init__(self, *args, **kwargs):
super(PlanetForm, self).__init__(*args, **kwargs)
sample_choices = list(
Sampletype.objects.filter(sample_option=True).values_list('id', 'name')
)
# set these choices on the 'samples' field.
self.fields['samples'].choices = sample_choices

How to create a model with ManyToOne like fields?

I'm trying to figure out how to properly create a model which has to have two attributes (Models) and it should meet some conditions.
So the model is called Job.
The Job represents buying a translation from one language (model Language) to another language (model Language too).
Each Job have to have exactly 1 language_from attribute and exactly 1 language_to attribute.
My old models:
class Language(models.Model):
shortcut = models.CharField(max_length=40)
name = models.CharField(max_length=40)
def __str__(self):
return self.name
class Job(models.Model):
customer = models.ForeignKey(User, related_name='orders')
translator = models.ForeignKey(User, related_name='jobs',null=True)
price = models.FloatField(null=True,blank=True)
language_from = # Didn't know what to put here
language_to = # Didn't know what to put here
def __str__(self):
return '{}: {}'.format(self.customer,)
I was searching for a way how to make it work, I though about some ManyToOne field but I've realised that there is no such field. So I've been adviced to rebuilt my models this way:
There will be no language_from and language_to attributes in Job.
There will be to and from attributes in model Language which
will be ForeignKeys.
So the Job would not have language_from/to attributes, instead of that, there would be attributes in Language model:
class Language(models.Model):
shortcut = models.CharField(max_length=40)
name = models.CharField(max_length=40)
job_from = models.ForeignKey('Job',related_name='language_form',null=True)
job_to = models.ForeignKey('Job',related_name='language_to',null=True)
def __str__(self):
return self.name
This way probably would work correctly but there are many problems.
I can't tell the Django that every Job has to have exactly on
language_form and language_to
There are problems when trying to create a JobCreationForm because I can't add language_from/to fields inside a class Meta:
I think that it is not intuitive and there will be more problems which I don't know yet about
.
class JobCreationForm(forms.ModelForm):
description = forms.CharField(widget=forms.Textarea(attrs={'placeholder': 'Specification'}))
file = forms.FileField()
class Meta:
model = Job
fields = (
'language_from','language_to', 'description', 'file', 'specialist'
)
Exception Value:
Unknown field(s) (language_from, language_to) specified for Job
Do anybody knows what should I do?
First of all, you can't use related name in your fields().
So change
fields = (
'language_from','language_to', 'description', 'file', 'specialist'
)
into
fields = (
'job_from','job_to', 'description', 'file', 'specialist'
)
Next, if you want to have for each job a language from & a language to, these are mandatory fields, so you can do this:
job_from = models.ForeignKey('Job',related_name='language_form')
job_to = models.ForeignKey('Job',related_name='language_to')
(https://docs.djangoproject.com/en/1.9/ref/forms/fields/#required)

Django get attributes from foreign key's class

I would like to show one attribute from another class. The current class has a foreign key to class where I want to get the attribute.
# models.py
class Course(models.Model):
name = models.CharField(max_length=100)
degree = models.CharField(max_length=15)
university = models.ForeignKey(University)
def __unicode__(self):
return self.name
class Module(models.Model):
code = models.CharField(max_length=10)
course = models.ForeignKey(Course)
def __unicode__(self):
return self.code
def getdegree(self):
return Course.objects.filter(degree=self)
# admin.py.
class ModuleAdmin(admin.ModelAdmin):
list_display = ('code','course','getdegree')
search_fields = ['name','code']
admin.site.register(Module,ModuleAdmin)
So what i'm trying to do is to get the "degree" that a module has using the "getdegree". I read several topics here and also tried the django documentation but i'm not an experienced user so even I guess it's something simple, I can't figure it out. Thanks!
It is pretty straight forward.
Try this:
def getdegree(self):
return self.course.degree
Documentation here
You can do this safely because course is not a nullable field. If it were, you should have checked for existence of object before accessing its attribute.

admin template for manytomany

I have a manytomany relationship between publication and pathology. Each publication can have many pathologies. When a publication appears in the admin template, I need to be able to see the many pathologies associated with that publication. Here is the model statement:
class Pathology(models.Model):
pathology = models.CharField(max_length=100)
def __unicode__(self):
return self.pathology
class Meta:
ordering = ["pathology"]
class Publication(models.Model):
pubtitle = models.TextField()
pathology = models.ManyToManyField(Pathology)
def __unicode__(self):
return self.pubtitle
class Meta:
ordering = ["pubtitle"]
Here is the admin.py. I have tried variations of the following, but always
get an error saying either publication or pathology doesn't have a foreign key
associated.
from myprograms.cpssite.models import Pathology
class PathologyAdmin(admin.ModelAdmin):
# ...
list_display = ('pathology', 'id')
admin.site.register(Pathology, PathologyAdmin)
class PathologyInline(admin.TabularInline):
#...
model = Pathology
extra = 3
class PublicationAdmin(admin.ModelAdmin):
# ...
ordering = ('pubtitle', 'year')
inlines = [PathologyInline]
admin.site.register(Publication,PublicationAdmin)
Thanks for any help.
Unless you are using a intermediate table as documented here http://docs.djangoproject.com/en/dev/ref/contrib/admin/#working-with-many-to-many-intermediary-models, I don't think you need to create an Inline class. Try removing the line includes=[PathologyInline] and see what happens.
I realize now that Django is great for the administration (data entry) of a website, simple searching and template inheritance, but Django and Python are not very good for complex web applications, where data is moved back and forth between a database and an html template. I have decided to combine Django and PHP, hopefully, applying the strengths of both. Thanks for you help!
That looks more like a one-to-many relationship to me, tho I'm somewhat unclear on what exactly Pathologies are. Also, so far as I understand, Inlines don't work on manytomany. That should work if you flip the order of the models, remove the manytomany and add a ForeignKey field to Publication in Pathology.
class Publication(models.Model):
pubtitle = models.TextField()
def __unicode__(self):
return self.pubtitle
class Meta:
ordering = ["pubtitle"]
class Pathology(models.Model):
pathology = models.CharField(max_length=100)
publication = models.ForeignKey(Publication)
def __unicode__(self):
return self.pathology
class Meta:
ordering = ["pathology"]

Categories