Django override-able abstract field for inlineformset - django

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!

Related

two-stage submission of django forms

I have a django model that looks something like this:
class MyModel(models.Model):
a = models.BooleanField(default=False)
b = models.CharField(max_length=33, blank=False)
c = models.CharField(max_length=40, blank=True)
and a corresponding form
class MyForm(ModelForm):
class Meta:
model = MyModel
Asking the user to fill in the form is a two phase process. First I ask whether a is True or False. After a submit (would be better maybe with ajax, but keep it simple at first) I can fill in a dropdown list with choices for b and decide whether or not to show c as an option. No need to show a as a choice any more, it's descriptive text.
So I want to present a form twice, and the same model is behind it, slowly being filled in. First choose a, then be reminded that a is True and be asked about b and c. Or that a is False and be asked about only b. After submitting this second form, I save the object.
I'm not clear how best to do this in django. One way is to make two separate form classes, one of which has hidden fields. But if the same model is behind both and the model has required fields, I'm anticipating this will get me in trouble, since the first form won't have satisified the requirement that b be non-empty. In addition, there's a small amount of fragility introduced, since updating the model requires updating two forms (and a probably at least one view).
Alternatively, I could use non-model forms and have full freedom, but I'd love to believe that django has foreseen this need and I can do it easier.
Any suggestions on what the right idiom is?
You can use Form Wizard from form-tools for that: https://django-formtools.readthedocs.io/en/latest/wizard.html
It works rather simple by defining multiple forms and combining them. Then in the end, you can use the data to your liking with a custom done() form. The docs tell you everything. You can use JS to hide some of your fields for the super quick approach (utilize localStorage for example).

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.

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

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?...

Building a list of ModelForms based on relation

I think this is best explained with some simple model code (I'm writing this from scratch so possible syntax issues - unimportant here):
class Car(models.Model)
make = models.CharField(...)
model = models.CharField(...)
class StatisticType(models.Model):
name = models.CharField(...)
class Statistic(models.Model)
car = models.ForeignKey('Car')
stype = models.ForeignKey('StatisticType')
data = models.CharField(...)
class Meta:
unique_together = (('car', 'stype'),)
We have a car with some hard-coded stats and we have some database controlled statistics. I might add Colours, Wheel Size, etc. The point is it's editable from the admin so neither I or the client need to climb through the data, but it's limited so users can only pick one of each stat (you can't define "Colours" twice).
So I'm trying to write the data input form for this now and I want a list of optional ModelForms that I can chuck on the page. I've got the simplest ModelForm possible:
class StatisticForm(forms.ModelForm):
class Meta:
model = Statistic
The tricky part (in my head) is generating an instance of this ModelForm for each StatisticType, regardless of it existing yet. That is to say if a Car object doesn't have a Colour assigned to it, the form still shows. Similarly, if it does, that instance of a Statistic is loaded in the ModelForm.
In my view, how do I generate a list of these things, regardless of there being a pre-existing instance of any given Statistic?
This seems like it should be a stupidly simple thing to do but it's late on Friday and everything looks skwonky.
Sounds like you might want to leverage an inline model formset factory.
That would allow you to create as many instances of your Statistic object as you need. If you're needing to create instances of your StatisticType on the fly, that's a bit different.
When Django instantiates forms, for a foreign key, m2m or choice field, it will only accept choices that it deems "valid", and will complain if you add a choice using JavaScript that doesn't exist in a related model or set of choices server-side.
So, if you need to make StatisticTypes on the fly, and then populate formset instances with this new value, I would suggest using Knockout.js. It's very good at keeping lots of DOM elements in sync when data changes.

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.