What is the read/write equivalent of serializers.StringRelatedField? - django

In Django Rest Framework 3, I want to return the unicode value of a pk relationship, the way you can using a serializer.StringRelatedField, but I need the value to be writable, too. StringRelatedField is read only.
I don't care if the API accepts the pk, or the string value, on the PUT (though accepting the string would be nifty, and would save me grabbing all the pks!). The API just needs to return the unicode string value on the GET.
I'm thinking PrimaryKeyRelatedField might be the way to go, but what does the query look like?
For instance, if the model I want is "Model", and I want Model.name to be serialized, what does this command look like:
name = serializers.PrimaryKeyRelatedField(queryset=Model.objects.get(pk=????))
I'm struggling because I don't know how to get the pk from the serializer object in order to query the related model ...
That's presuming PrimaryKeyRelatedField is what I need, of course. Which may be totally wrong.
Thanks
John
Here are example models as requested, slightly changed for clarity:
class CarModel(models.Model):
name = models.CharField(max_length=100,unique=True)
def __str__(self):
return self.name
class Car(models.Model):
name = models.CharField(max_length=100)
make = models.ForeignKey(CarMake)
car_model = models.ForeignKey(CarModel)
class CarSerializer(serializers.ModelSerializer):
car_model = serializers.StringRelatedField() //like this, but read/write
class Meta:
model = Car
In this example I'm serializing Car, trying to return a string value of CarModel that can be updated with a select dropdown in a form.
If I use different serializers, one for POST that expects the PK and one for everything else that returns the string, the select directive in the form gets very messy.
So, ideally, I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK.

"I just want to be able to POST the string value, and have the API complete the lookup and save the string as a PK."
That would imply that 'name' should be unique. If it isn't unique then the lookup might return several instances. In the example you currently have 'name' isn't unique, but if it was then you could use...
car_model = serializers.SlugRelatedField(queryset=..., lookup_field='name')
I'm not convinced if that's actually what you want though. The best way to clarify these sorts of questions is typically to forget about the code for a moment, and just focus on a precise description of what you want the input and output representations to look like?...

Related

Add ModelForm Fields as Attribute to Object

I have a ModelForm for my Risk set up as:
class RiskForm(forms.ModelForm):
class Meta:
model = Risk
fields = '__all__'
def __init__(self, *args, **kwargs):
progid = kwargs.pop('progid')
super(RiskForm, self).__init__(*args,**kwargs)
dict_of_fields = {}
all_char = Program.objects.get(id=progid).char_set.all()
for char in all_char:
c = []
for cat in char.cat_set.all():
c.append( (cat.label, cat.label) )
dict_of_fields[char.label] = c
self.fields[char.label] = forms.ChoiceField(c)
Where the Risk Object is defined as:
class Risk(models.Model):
program = models.ForeignKey(Program, on_delete=models.CASCADE)
label = models.CharField(max_length=200, default='')
def __str__(self):
return self.label
However, I want to store the extra fields that I have created into my database under my Risk object.
As I have it now, it only stores the two attributes 'program' and 'label'. However, I also want to store the answers to the characteristics into my database for later usage.
For more information about how I've set things up: Django Form Based on Variable Attributes
And a print screen of my ModelForm: https://gyazo.com/89c9833613dbcc7e8d27cc23a3abaf72
Is it possible to store all 6 answers under my Risk Object in my database? If so, how do I do that?
A form in Django is a user interface object: it gives the user a set of fields to type answers into, checks that the data which they have supplied is valid, and converts it from text to the desired Python data-type (int, date, etc.). It does not have to relate to any particular model, and often doesn't. For example, an online shop is likely to have purchase selection forms which add data concerning possible orders into the user's session, rather than immediately performing any sort of update on a Product or Stock object. That happens later, at checkout.
A ModelForm assumes there is a Model to Form relationship. It is typically the right way to go when there is a simple relationship between the user's answers and a single model instance.
If you have a situation where the user's answers direct the creation of multiple database objects with a less direct relationship to any particular object, you probably don't want a ModelForm, just a Form. Also probably not a model-based view, but a function-based view. You can then do anything you need to between the view picking up the parameters from the URL parser, and displaying the form to the user. Likewise, anything between the view determining that the user's POST data is valid and telling the user whether his submitted request succeeded (or not, and why).
In this case I'm not clear how you want to store all six answers. If there's a predetermined fairly small set of answers you could have a single object with six? ten? possible sets of fields which are nullable to indicate that this object doesn't have that entity. Or, probably better, you could create a set of Answer objects each of which has a Foreign Key relationship to the Risk object, and later refer to Risk.answer_set (all the Answer objects which have a foreign key relationship to your risk object). This is open-ended, a Risk object can have anything from zero to bignum associated Answers.

Changing the field name when using Django rest framework

I'm feeding serialized data from the Django rest framework to a Javascript pivot table on my site. If I have a variable called 'created_on', the DRF uses that as the field name. What I want to display in my pivot table is the label which will be converted to 'Created On'.
As an example, my output from DRF is the following:
[{"created_on": "2016-04-23"}, {"created_on": "2016-05-23"}]
What I want is:
[{"Created on": "2016-04-23"}, {"Created on": "2016-05-23"}]
Is this possible without me overriding the serialization process?
No, its not possible (currently) without overriding the serialization process.
Why it is not possible?
This is because the alternate name which you want to use for created_on contains whitespace and its not possible to define a field in your serializer having whitespaces in it. Also, there is no functionality currently to provide alternate name for a field to be used in serialization process.
Possible Solution:
You can override the to_representation() method of your serializer and there add a Created On key having value equal to the value of created_on key. Then all the serialized objects will contain a key Created On.
class MySerializer(serializers.ModelSerializer):
...
def to_representation(self, obj):
primitive_repr = super(MySerializer, self).to_representation(obj)
primitive_repr['Created On'] = primitive_repr['created_on']
return primitive_repr
What if the alternate name did not contain any whitespaces?
Had the alternate name did not contain any spaces between them, you could then have used SerializerMethodField() with source argument.
You could have done something like:
class MySerializer(serializers.ModelSerializer):
alternate_name = serializers.SerializerMethodField(source='created_on')
class Meta:
model = MyModel
fields = (.., 'alternate_name')

Django override-able abstract field for inlineformset

Intro
Hello!!!!! I've looked for the solution to this problem already - the nearest thing I've found so far is this, and it doesn't have a full enough answer for me.
Background
The following is a contrived example to make my question clearer.
I am interested in using inlineformset_factory in a situation where each entry in the form can correspond to different subclasses of the same parent model. For example, say I have a form model that has a number of input items:
class Form(models.Model):
name = models.CharField(max_length=30)
Then, I have an Input model and its subclasses:
class Input(models.Model):
class Meta:
abstract = True
form = models.ForeignKey(Form)
name = models.CharField(max_length=30)
class ShortInput(Input):
value = models.CharField(max_length=20)
class TweetInput(Input):
value = models.CharField(max_length=140)
class BinaryInput(Input):
value = models.TextField(blank=True)
Essentially, it's a form with various types of data. It's important that ShortInput instances only take up 20 characters of space in the backend, and BinaryInput instances have a huge amount of storage space. Note also that every subclass of Input will always have a value field, although they may be of different lengths/types.
What I'd like to do now is use inlineformset_factory to build the Form model. Looks like this:
form = inlineformset_factory(
Form,
Input,
fk_name='form',
fields=['value']
)
The intention here is to build a form where I can edit the values of every Input entry in the database.
The problem
For the inlineformset_factory call, Django complains that Input has no field named value.
The question
I feel I should be able to deal with this problem because I can guarantee every Input subclass will have a value attribute - I just need to tell this to Django in some way. The ideal thing I'm looking for is something like this:
class Input(models.Model):
class Meta:
abstract = True
guaranteed_subclass_fields = [
'value',
]
...
In other words, a way of telling Django it can expect a value field in subclasses.
The next best thing I'm looking for is some other way of specifying to Django that it can rely on a value field existing.
Finally, if none of this is possible, I'm looking for some other (potentially hackish) way of passing my Input class with a value field to the inlineformset_factory function - perhaps ignoring the error or something of the sort. (Although regardless of how hackish the solution is, I would like it to involve the use of inlineformset_factory)
Thanks in advance for any help!

Customizing a date field in a model form

Let me start out by saying this question is basic, and almost certainly has been answered elsewhere if I only could find exactly what I need.
I have users who will be entering dates in some screwy formats, which won't be recognized by the usual validation code for the DateField (for instance, 2014/4 which I will want to convert to 2014-4-01 internally). Do I need to mess with the code to clean, validate, or both for this, and if I redefine those functions do I need to explicitly call super?
My thought was that maybe I should give the model field and the form field different names, and somehow fill the model field with the form field data when I process the form... but the how of that is vague.
class Person(models.Model):
(some other fields)
date_of_arrival = models.DateField(blank=True, null=True)
class ClientForm(forms.ModelForm):
(some other fields)
date_of_arrival = forms.DateField(required=False, help_text="Date of arrival in town")
class Meta:
model = Person
fields = (..., date_of_arrival, ...)
$date = date('Y-m-d',$urvalue);
You need to implement a _clean() method in your form, where the name of the field is before the _, so in your example it would be date_of_arrival_clean().
In this method, do all your validations. I would recommend the dateutil package, which provides parse method that is designed to take a string that could possibly be a date, and convert it to a date. Using this method you will not have to write the mundane logic, instead your entire method would be:
def date_of_arrival_clean(self):
user_input = self.cleaned_data['date_of_arrival']
try:
d = parse(user_input)
except (ValueError, TypeError):
raise forms.ValidationError('{} is not a valid date'.format(user_input))
return user_input
My thought was that maybe I should give the model field and the form
field different names
You don't have to do this ... you can keep the same fields as in your model form and still implement the _clean() method as described above.
Keep in mind that if your form is a ModelForm, then django will also do database validation when try to validate the form.

Dynamic model choice field in django formset using multiple select elements

I posted this question on the django-users list, but haven't had a reply there yet.
I have models that look something like this:
class ProductGroup(models.Model):
name = models.CharField(max_length=10, primary_key=True)
def __unicode__(self): return self.name
class ProductRun(models.Model):
date = models.DateField(primary_key=True)
def __unicode__(self): return self.date.isoformat()
class CatalogItem(models.Model):
cid = models.CharField(max_length=25, primary_key=True)
group = models.ForeignKey(ProductGroup)
run = models.ForeignKey(ProductRun)
pnumber = models.IntegerField()
def __unicode__(self): return self.cid
class Meta:
unique_together = ('group', 'run', 'pnumber')
class Transaction(models.Model):
timestamp = models.DateTimeField()
user = models.ForeignKey(User)
item = models.ForeignKey(CatalogItem)
quantity = models.IntegerField()
price = models.FloatField()
Let's say there are about 10 ProductGroups and 10-20 relevant
ProductRuns at any given time. Each group has 20-200 distinct
product numbers (pnumber), so there are at least a few thousand
CatalogItems.
I am working on formsets for the Transaction model. Instead of a
single select menu with the several thousand CatalogItems for the
ForeignKey field, I want to substitute three drop-down menus, for
group, run, and pnumber, which uniquely identify the CatalogItem.
I'd also like to limit the choices in the second two drop-downs to
those runs and pnumbers which are available for the currently
selected product group (I can update them via AJAX if the user
changes the product group, but it's important that the initial page
load as described without relying on AJAX).
What's the best way to do this?
As a point of departure, here's what I've tried/considered so far:
My first approach was to exclude the item foreign key field from the
form, add the substitute dropdowns by overriding the add_fields
method of the formset, and then extract the data and populate the
fields manually on the model instances before saving them. It's
straightforward and pretty simple, but it's not very reusable and I
don't think it is the right way to do this.
My second approach was to create a new field which inherits both
MultiValueField and ModelChoiceField, and a corresponding
MultiWidget subclass. This seems like the right approach. As
Malcolm Tredinnick put it in
a django-users discussion,
"the 'smarts' of a field lie in the Field class."
The problem I'm having is when/where to fetch the lists of choices
from the db. The code I have now does it in the Field's __init__,
but that means I have to know which ProductGroup I'm dealing with
before I can even define the Form class, since I have to instantiate the
Field when I define the form. So I have a factory
function which I call at the last minute from my view--after I know
what CatalogItems I have and which product group they're in--to
create form/formset classes and instantiate them. It works, but I
wonder if there's a better way. After all, the field should be
able to determine the correct choices much later on, once it knows
its current value.
Another problem is that my implementation limits the entire formset
to transactions relating to (CatalogItems from) a single
ProductGroup.
A third possibility I'm entertaining is to put it all in the Widget
class. Once I have the related model instance, or the cid, or
whatever the widget is given, I can get the ProductGroup and
construct the drop-downs. This would solve the issues with my
second approach, but doesn't seem like the right approach.
One way of setting field choices of a form in a formset is in the form's __init__ method by overwriting the self.fields['field_name'].choices, but since a more dynamic approach is desired, here is what works in a view:
from django.forms.models import modelformset_factory
user_choices = [(1, 'something'), (2, 'something_else')] # some basic choices
PurchaserChoiceFormSet = modelformset_factory(PurchaserChoice, form=PurchaserChoiceForm, extra=5, max_num=5)
my_formset = PurchaserChoiceFormSet(self.request.POST or None, queryset=worksheet_choices)
# and now for the magical for loop
for choice_form in my_formset:
choice_form.fields['model'].choices = user_choices
I wasn't able to find the answer for this but tried it out and it works in Django 1.6.5. I figured it out since formsets and for loops seem to go so well together :)
I ended up sticking with the second approach, but I'm convinced now that it was the Short Way That Was Very Long. I had to dig around a bit in the ModelForm and FormField innards, and IMO the complexity outweighs the minimal benefits.
What I wrote in the question about the first approach, "It's straightforward and pretty simple," should have been the tip-off.