DRF show ForeignKey field choices - django

What's the best way to go about letting a frontend user know about ForeignKey field choices using Django Rest Framework? In the Browsable API these fields have a dropdown widget with all the existing objects as choices.
A custom metadata class could return the available choices for each field but the request could be very slow if there are millions of objects.
Suppose you have a model similar to below and there's only 5 unit objects. How would you go about listing the unit choices?
class OrderLine(models.Model):
order = models.ForeignKey(Order)
product = models.ForeignKey(Product)
unit = models.ForeignKey(Unit)

I ended up implementing a custom metadata class that adds foreign key choices to an OPTIONS request based on the serializer attribute extra_choice_fields. This way you can choose which fields to provided choices for on each serializer and which to not include (ex. exclude fields with a lot of objects).
from rest_framework.metadata import SimpleMetadata
from rest_framework.relations import ManyRelatedField, RelatedField
from django.utils.encoding import force_text
class ChoicesMetadata(SimpleMetadata):
def get_field_info(self, field):
field_info = super().get_field_info(field)
if (isinstance(field, (RelatedField, ManyRelatedField)) and
field.field_name in getattr(field.parent.Meta, 'extra_choice_fields', [])):
field_info['choices'] = [{
'value': choice_value,
'display_name': force_text(choice_name, strings_only=True)
} for choice_value, choice_name in field.get_choices().items()]
return field_info

Related

How can I access parent model fields in a form using the child model?

For context, I'm trying to create a form that allows users to upload info about their own custom Pokemon. Basically, they are creatures that you can catch, name, and level up. To draw a comparison, it is a similar concept to dogs; there are labradors, German Shepherds, huskies, etc. that would be variations of a base Dog model, but then each individual would have a name and other defining characteristics.
I've created Pokemon and CustomPokemon models and imported the latter into my forms.py file. I'm trying to access some parent fields but am unable to:
from django import forms
from .models import CustomPokemon
class PokemonForm(forms.ModelForm):
class Meta:
model = CustomPokemon
fields = ['pokemon.poke_name', 'name', 'level']
The poke_name field is inherited from the parent Pokemon model while the other two fields belong to the CustomPokemon model. I'm getting this FieldError:
Unknown field(s) (pokemon.poke_name) specified for CustomPokemon.
The issue isn't resolved by using poke_name, so I'm curious how I can access the parent model's fields so they can be displayed in the form.
First option
If you just want a dropdown displaying the field poke_name, what you could do is to define a __str__ method inside Pokemon model like this:
class Pokemon(model.Model):
...
def __str__(self):
return self.poke_name
Then, you can define the form as follows:
class PokemonForm(forms.ModelForm):
class Meta:
model = CustomPokemon
fields = ['pokemon', 'name', 'level']
And you will get a dropdown displaying all the poke_name of your database, where you can choose your foreign key. It would be better if poke_name is a unique field so that the foreign key can be clearly identified.
Second option
If you need more freedom, you could manually define a custom field as follows:
class PokemonForm(forms.ModelForm):
poke_name = forms.CharField()
class Meta:
model = CustomPokemon
fields = ['name', 'level']
Then, when validating the form, you should take care of whether the entry exists and/or create it:
if form.is_valid():
form.instance.pokemon = Pokemon.objects.get_or_create(cname=form.cleaned_data['poke_name'])
form.save()

How to design a Django API to handle a "Dynamic" form?

I have built an Angular form that contains a form array. So the user has the control to add fields or delete fields from the form. What I am trying to understand is how I can design a Django API that can handle the post for this kind of dynamic form?
I do not want the user to create an account on the website in order to place his order. Is that even possible?
You should be more concerned about how to model your data, than you can think about your interface. Here a few options for modeling your data:
Option One is to use regular Django ORM, and in this case you may use the JSONField for any dynamic properties.
Option two is to use any schemaless data model, such as document-based databases(e.g MongoDB).
Here a simple example, on how to use Django's JSONField:
your model:
class Order(models.Model):
customer = models.ForeignKey(User, on_delete=models.CASCADE)
#any additional static fields
properties = JSONField()
your view:
def create_order_view(request):
if request.method == "POST":
#do your validation
Order.objects.create(user=request.user, properties=request.POST["properties"])
return Response(status=200)
this example is totally incomplete as you have to add validation error handling, and it is a better idea to use Django rest-framework for constructing your API.
Finally as I said there many option to model your data, in addition to what I mentioned above there are many other. To decide what model to use, you have to know how your data gonna be consumed, so you can optimze for query time, in addition there are many other factors but this is out of scope of this asnwer.
For me, I used Django-RESTframework to build the api.
The way to achieve this is simple, just create the model and iterate through the items which is the dynamic part, and assign the Foreignkey field to obj.id created. First, I created the main model instance, then created the instances of the child instances. I will use Order and Item to demonstrate the idea, The Item instance will have Foreinkey field to Order model.
In the Item model, add "related_name" argument to the Foreinkey field
order = models.ForeignKey(Order, related_name='items',on_delete=models.CASCADE)
serializers.py
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = [
....your fields...
]
class OrderSerializer(serializers.ModelSerializer):
items = ItemSerializer(many=True)
class Meta:
model = Order
fields = [
'order', ....
]
def create(self, validated_data):
items_data = validated_data.pop("items")
order = Order.objects.create(**validated_data)
order.total_fees = order.delivery_fees
for item in items_data:
i = Item.objects.create(order=order, **item)
return order

django-filter with django autocomplete light

Has anyone succesfully used dal and django-filter together?
Below attempt is mine,
I tried to use filterset_factory, supplying model class and fields list, then I tried to use futuremodelform.
I got ,
ModelForm has no model class specified.
I think it's just one of many errors to occur.
Anybody done that before, I have to use filterset_factory, and create dynamic classes from arguments, I also want to override widgets so dal widgets can be used.
#testing filterset
from dal import autocomplete
from django.db import models
class PanFilterSet(django_filters.FilterSet):
filter_overrides = {
models.ForeignKey: {
'filter_class': autocomplete.ModelSelect2,
},
}
def pan_filterset_factory(model,fields):
meta = type(str('Meta'), (object,), {'model': model,'fields':fields,'form':autocomplete.FutureModelForm})
filterset = type(str('%sFilterSet' % model._meta.object_name),
(PanFilterSet,), {'Meta': meta})
return filterset
searchFormFilterSet = pan_filterset_factory(self.model_class,self.final_search_fields)
f = searchFormFilterSet(self.request.GET, queryset=self.get_queryset())
print f.form.as_p()
I'm not very familiar with DAL, but I contribute to django-filter and have a decent understanding of its internals. A few notes:
The filter_class in your filter_overrides should be a filter, not a widget. You can provide additional arguments (such as the widget) through the extra key, as seen here. Any parameter that does not belong to the filter is automatically passed to the underlying form field.
Using an override isn't the right approach anyway, as the widget needs a field-specific endpoint to perform autocompletion. Since the endpoint is field-specific, it's not applicable to all ForeignKeys.
django-filter uses regular Forms, not ModelForms, so an appropriate Meta inner class would not be constructed. FutureModelForm doesn't seem to provide autocomplete functionality anyway - it seems irrelevant?
Your factory will have to generate your autocomplete filters manually - something like the following:
def dal_field(field_name, url):
return filters.ModelChoiceFilter(
name=field_name,
widget=autocomplete.ModelSelect2(url=url),
)
def dal_filterset_factory(model, fields, dal_fields):
attrs = {field: dal_field(field, url) for field, url in dal_fields.items()}
attrs['Meta'] = type(str('Meta'), (object,), {'model': model,'fields': fields})
filterset = type(str('%sFilterSet' % model._meta.object_name),
(FilterSet,), attrs)
return filterset
# Usage:
# mapping of {field names: autocomplete endpoints}.
dal_fields = {'birth_country': 'country-autocomplete'}
fields = ['list', 'or', 'dict', 'of', 'other', 'fields']
SomeModelFilterSet = dal_filterset_factory(SomeModel, fields, dal_fields)
The fields in attrs use the declarative API. More info in the docs.

Django admin: don't send all options for a field?

One of my Django admin "edit object" pages started loading very slowly because of a ForeignKey on another object there that has a lot of instances. Is there a way I could tell Django to render the field, but not send any options, because I'm going to pull them via AJAX based on a choice in another SelectBox?
You can set the queryset of that ModelChoiceField to empty in your ModelForm.
class MyAdminForm(forms.ModelForm):
def __init__(self):
self.fields['MY_MODEL_CHOIE_FIELD'].queryset = RelatedModel.objects.empty()
class Meta:
model = MyModel
fields = [...]
I think you can try raw_id_fields
By default, Django’s admin uses a select-box interface () for fields that are ForeignKey. Sometimes you don’t want to incur the overhead of having to select all the related instances to display in the drop-down.
raw_id_fields is a list of fields you would like to change into an Input widget for either a ForeignKey or ManyToManyField
Or you need to create a custom admin form
MY_CHOICES = (
('', '---------'),
)
class MyAdminForm(forms.ModelForm):
my_field = forms.ChoiceField(choices=MY_CHOICES)
class Meta:
model = MyModel
fields = [...]
class MyAdmin(admin.ModelAdmin):
form = MyAdminForm
Neither of the other answers worked for me, so I read Django's internals and tried on my own:
class EmptySelectWidget(Select):
"""
A class that behaves like Select from django.forms.widgets, but doesn't
display any options other than the empty and selected ones. The remaining
ones can be pulled via AJAX in order to perform chaining and save
bandwidth and time on page generation.
To use it, specify the widget as described here in "Overriding the
default fields":
https://docs.djangoproject.com/en/1.9/topics/forms/modelforms/
This class is related to the following StackOverflow problem:
> One of my Django admin "edit object" pages started loading very slowly
> because of a ForeignKey on another object there that has a lot of
> instances. Is there a way I could tell Django to render the field, but
> not send any options, because I'm going to pull them via AJAX based on
> a choice in another SelectBox?
Source: http://stackoverflow.com/q/37327422/1091116
"""
def render_options(self, *args, **kwargs):
# copy the choices so that we don't risk affecting validation by
# references (I hadn't checked if this works without this trick)
choices_copy = self.choices
self.choices = [('', '---------'), ]
ret = super(EmptySelectWidget, self).render_options(*args, **kwargs)
self.choices = choices_copy
return ret

UpdateView - Update a class that has OneToOne with User

Say I have a model called MyUser. It has some field, and one of them is this one:
user = OneToOneField(User, related_name='more_user_information')
I want to make a view to update this model, and I do the following:
Class AccountEdit(LoginRequiredMixin, UpdateView):
model = MyUser
form_class = MyUserForm
template_name = 'accounts/edit.html'
def get_object(self, queryset=None):
return self.model.objects.get(user=self.request.user)
Each field in MyUser renders fine for editing, except user. This one to one field becomse a select drop down box. What I like to do is to edit the fields on User model like first name or last name.
How can I achieve this while extending UpdateView? or perhaps shuold I use a FormView?
thanks
This problem is actually nothing to do with class based views or update view - its a basic issue that has been there since the beginning, which is:
ModelForms only edit the fields for one model, and don't recurse into
foreign keys.
In other words, if you have a model like this:
class MyModel(models.Model):
a = models.ForeignKey('Foo')
b = models.ForeignKey('Bar')
c = models.ForeignKey('Zoo')
name = models.CharField(max_length=200)
A model form will render three select fields, one for each foreign key, and these select fields will have all the values from those models listed - along with one text field for the name.
To solve this problem, you need to use InlineFormSets:
Inline formsets is a small abstraction layer on top of model formsets.
These simplify the case of working with related objects via a foreign
key.
You should use InlineFormSet from the excellent django-extra-views app. To do this, you'll create a view for the related object as well:
class MyUserInline(InlineFormSet):
model = MyUser
def get_object(self):
return MyUser.objects.get(user=self.request.user)
class AccountEditView(UpdateWithInlinesView):
model = User
inlines = [MyUserInline]
Another option is django-betterforms's Multiform and ModelMultiForm.
Example:
class UserProfileMultiForm(MultiForm):
form_classes = {
'user': UserForm,
'profile': ProfileForm,
}
It works with generic CBV (CreateView, UpdateView, WizardView).