Django Rest Framework serialization fails on post requests - django

I have a seializer class
class StudentJournalSerializer(serializers.ModelSerializer):
class Meta:
model = StudentJournalModel
fields = (
'value',
'date',
'discipline',
'para_number',
'student',
'is_module'
)
Which I cant get to work as I need it to.
I want it to display not the pk values for ForeignKey fields but actual field values plus this class should work for post methods as well.
Usually I used to add:
student = serializers.CharField(
source='student.username'
)
discipline = serializers.CharField(
source='discipline.discipline'
)
para_number = serializers.CharField(
source='para_number.para_position'
)
However it only works with GET read_only=True
But I need to use it during post requests from client app to create new objects in database so it obviously wont work. I read that i need to write the .create() method to handle such case but I dont really understand how it works and what i need to overwrite there, so I would really appreciate if someone can explain how it should be and why.
Attaching code for the model as well:
class StudentJournalModel(models.Model):
value = models.CharField(
max_length=55,
blank=True,
null=True,
verbose_name="Value",
default=''
)
date = models.DateField(
verbose_name="Date"
)
discipline = models.ForeignKey(
'department.Disciplines',
verbose_name="Discipline"
)
para_number = models.ForeignKey(
'department.ParaTime',
verbose_name="Class #"
)
student = models.ForeignKey(
User,
verbose_name="Student"
)
is_module = models.BooleanField(
verbose_name="Module value"
)
def __unicode__(self):
return u"%s, %s, %s" % (self.date, self.discipline, self.student.get_full_name())

You are looking for SlugRelatedField.
Note that you need to make sure that the slug field has a unique constraint.

If you want to expose all the fields for the model it's enough to just say:
class StudentJournalSerializer(serializers.ModelSerializer):
class Meta:
model = StudentJournalModel
depth = 1
From docs: The depth option should be set to an integer value that indicates the depth of relationships that should be traversed before reverting to a flat representation.
This will work to GET the nested resource also, for POST you will need to send the ids for related fields, it should be enough to create the new object. The student, discipline and para_number should already exist in database, if any of them should also be created then you need to write .create() method yourself see this nice little exemple from docs

Related

Django annotate M2M object with field from through model

Let's say I have the following models:
class Color(models.Model):
name = models.CharField(max_length=255, unique=True)
users = models.ManyToManyField(User, through="UserColor", related_name="colors")
class UserColor(models.Model):
class Meta:
unique_together = (("user", "color"), ("user", "rank"))
user = models.ForeignKey(User, on_delete=models.CASCADE)
color = models.ForeignKey(Color, on_delete=models.CASCADE)
rank = models.PositiveSmallIntegerField()
I want to fetch all users from the database with their respective colors and color ranks. I know I can do this by traversing across the through model, which makes a total of 3 DB hits:
users = User.objects.prefetch_related(
Prefetch(
"usercolor_set",
queryset=UserColor.objects.order_by("rank").prefetch_related(
Prefetch("color", queryset=Color.objects.only("name"))
),
)
)
for user in users:
for usercolor in user.usercolor_set.all():
print(user, usercolor.color.name, usercolor.rank)
I discovered another way to do this by annotating the rank onto the Color objects, which makes sense because we have a unique constraint on user and color.
users = User.objects.prefetch_related(
Prefetch(
"colors",
queryset=(
Color.objects.annotate(rank=F("usercolor__rank"))
.order_by("rank")
.distinct()
),
)
)
for user in users:
for color in user.colors.all():
print(user, color, color.rank)
This approach comes with several benefits:
Makes only 2 DB hits instead of 3.
Don't have to deal with the through object, which I think is more intuitive.
However, it only works if I chain distinct() (otherwise I get duplicate objects) and I'm worried this may not be a legit approach (maybe I just came up with a hack that may not work in all cases).
So is the second solution legit? Is there a better way to it? Or should I stick to the first one?

How to model two relationships, and enforce that the pair is unique

I have a DRF model like so:
class Party(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
name = models.CharField(unique=True, blank=False, null=False)
And two and only two parties can form an integration, which I currently have modeled like this:
class Integration(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4)
party_a = models.ForeignKey('Party')
party_b = models.ForeignKey('Party')
class Meta:
managed = True
unique_together = (('party_a', 'party_b'), ('party_b', 'party_a'))
The pair of party_a and party_b has to be unique; which party is which doesn't matter. So, Party 123 and Party 456 can only be integrated once. The unique_together doesn't help me out much here, so I setup a validator to do that.
class IntegrationSerializer(serializers.HyperlinkedModelSerializer):
party_a = serializers.PrimaryKeyRelatedField(queryset=Party.objects.all())
party_b = serializers.PrimaryKeyRelatedField(queryset=Party.objects.all())
class Meta:
model = Integration
fields = ('id', 'party_a', 'party_b', 'enabled', 'active_date')
def validate(self, data):
if len(Integration.objects.filter(party_a=data['party_a'], party_b=data['party_b'])) > 0 or \
len(Integration.objects.filter(party_a=data['party_b'], party_b=data['party_a'])) > 0:
raise serializers.ValidationError('Integration already exists')
return data
I'm wondering if this is the best way to model this relationship? My current solution seems to work okay, but as I test, I keep finding things I need to fix. Like I forgot the validator would trigger on a PUT.
Any suggestions are appreciated...
Compare party_a and party_b, put the little one in party_a, and the large one in party_b. This makes only one instance of the pair of parties exists in db. You can overwrite the save method of serializer to ensure this relation.
P.S. queryset.count seems more elegant than len(queryset), and here you could use queryset.exists instead.

m2m field 'through' another model that contains two of the same fields

The basic idea is that I want to track training and have a roster for each training session. I would like to also track who entered each person in the roster hence a table rather than just an M2M to the Member model within Training.
So, here is what I currently have:
class Training( models.Model ):
name = models.CharField( max_length=100, db_index=True )
date = models.DateField( db_index=True )
roster = models.ManyToManyField( Member, through='TrainingRoster' )
class TrainingRoster( models.Model ):
training = models.ForeignKey( Training )
member = models.ForeignKey( Member )
## auto info
entered_by = models.ForeignKey( Member, related_name='training_roster_entered_by' )
entered_on = models.DateTimeField( auto_now_add = True )
The problem is that django doesn't like the "roster=models.m2m( Member, through='TrainingRoster') as there are two fields in TrainingRoster with a ForeignKey of Member. I understand why it is unhappy, but is there not a way to specify something like: through='TrainingRoster.member'. That doesn't work, but it seems like it should.
[I will admit that I am wondering if the "entered_by" and "entered_on" fields are the best for these models. I want to track who is entering each piece of information but possible a log table might be better than having the two extra fields in the TrainingRoster table. But that is a whole separate question. Though would make this question easier. :-) ]

Django Form with no required fields

I want to make a form used to filter searches without any field being required. For example given this code:
models.py:
class Message(models.Model):
happened = models.DateTimeField()
filename = models.CharField(max_length=512, blank=True, null=True)
message = models.TextField(blank=True, null=True)
dest = models.CharField(max_length=512, blank=True, null=True)
fromhost = models.ForeignKey(Hosts, related_name='to hosts', blank=True, null=True)
TYPE_CHOICES = ( (u'Info', u'Info'), (u'Error', u'Error'), (u'File', u'File'), (u'BPS', u'BPS'),)
type = models.CharField(max_length=7, choices=TYPE_CHOICES)
job = models.ForeignKey(Jobs)
views.py:
WHEN_CHOICES = ( (u'', ''), (1, u'Today'), (2, u'Two days'), (3, u'Three Days'), (7, u'Week'),(31, u'Month'),)
class MessageSearch(ModelForm): #Class that makes a form from a model that can be customized by placing info above the class Meta
message = forms.CharField(max_length=25, required=False)
job = forms.CharField(max_length=25, required=False)
happened = forms.CharField(max_length=14, widget=forms.Select(choices=WHEN_CHOICES), required=False)
class Meta:
model = Message
That's the code I have now. As you can see it makes a form based on a model. I redefined message in the form because I'm using an icontains filter so I didn't need a giant text box. I redefined the date mostly because I didn't want to have to mess around with dates (I hate working with dates! Who doesnt?) And I changed the jobs field because otherwise I was getting a drop down list of existing jobs and I really wanted to be able to search by common words. So I was able to mark all of those as not required
The problem is it's marking all my other fields as required because in the model they're not allowed to be blank.
Now in the model they can't be blank. If they're blank then the data is bad and I don't want it in the DB. However the form is only a filter form on a page to display the data. I'm never going to save from that form so I don't care if fields are blank or not. So is there an easy way to make all fields as required=false while still using the class Meta: model = Message format in the form? It's really handy that I can make a form directly from a model.
Also this is my first serious attempt at a django app so if something is absurdly wrong please be kind :)
You can create a custom ModelForm that suit your needs. This custom ModelForm will override the save method and set all fields to be non-required:
from django.forms import ModelForm
class SearchForm(ModelForm):
def __init__(self, *args, **kwargs):
super(SearchForm, self).__init__(*args, **kwargs)
for key, field in self.fields.iteritems():
self.fields[key].required = False
So you could declare your forms by simply calling instead of the ModelForm, e.g.:
class MessageForm(SearchForm):
class Meta:
model = Message
You could also pass empty_permitted=True when you instantiate the form, e.g.,
form = MessageSearch(empty_permitted=True)
that way you can still have normal validation rules for when someone does enter data into the form.
I would give a try to the django-filter module :
http://django-filter.readthedocs.io/en/develop/
fields are not required. these are filters actually. It would look like this :
import django_filters
class MessageSearch(django_filters.FilterSet):
class Meta:
model = Message
fields = ['happened', 'filename', 'message', '...', ]
# django-filter has its own default widgets corresponding to the field
# type of the model, but you can tweak and subclass in a django way :
happened = django_filters.DateFromToRangeFilter()
mandatory, hidden filters can be defined if you want to narrow a list of model depending on something like user rights etc.
also : setup a filter on a 'reverse' relationship (the foreignkey is not in the filtered model : the model is referenced elsewhere in another table), is easy, just name the table where the foreign key of the filtered model field is :
# the 'tags' model has a fk like message = models.ForeignKey(Message...)
tags= django_filters.<some filter>(name='tags')
quick extendable and clean to setup.
please note I didn't wrote this module, I'm just very happy with it :)

How do you set the initial value for a ManyToMany field in django?

I am using a ModelForm to create a form, and I have gotten the initial values set for every field in the form except for the one that is a ManyToMany field.
I understand that I need to give it a list, but I can't get it to work. My code in my view right now is:
userProfile = request.user.get_profile()
employer = userProfile.employer
bar_memberships = userProfile.barmembership.all()
profileForm = ProfileForm(
initial = {'employer': employer, 'barmembership' : bar_memberships})
But that doesn't work. Am I missing something here?
Per request in the comments, here's the relevant parts of my model:
# a class where bar memberships are held and handled.
class BarMembership(models.Model):
barMembershipUUID = models.AutoField("a unique ID for each bar membership",
primary_key=True)
barMembership = USStateField("the two letter state abbreviation of a bar membership")
def __unicode__(self):
return self.get_barMembership_display()
class Meta:
verbose_name = "bar membership"
db_table = "BarMembership"
ordering = ["barMembership"]
And the user profile that's being extended:
# a class to extend the User class with the fields we need.
class UserProfile(models.Model):
userProfileUUID = models.AutoField("a unique ID for each user profile",
primary_key=True)
user = models.ForeignKey(User,
verbose_name="the user this model extends",
unique=True)
employer = models.CharField("the user's employer",
max_length=100,
blank=True)
barmembership = models.ManyToManyField(BarMembership,
verbose_name="the bar memberships held by the user",
blank=True,
null=True)
Hope this helps.
OK, I finally figured this out. Good lord, sometimes the solutions are way too easy.
I need to be doing:
profileForm = ProfileForm(instance = userProfile)
I made that change, and now everything works.
Although the answer by mlissner might work in some cases, I do not think it is what you want. The keyword "instance" is meant for updating an existing record.
Referring to your attempt to use the keyword "initial", just change the line to:
bar_memberships = userProfile.barmembership.all().values_list('pk', flat=True)
I have not tested this with your code, but I use something similar in my code and it works.