Django REST Framework: default fields in browseable API form - django

I have a model:
class XCall(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
send_on = models.DateTimeField(default=datetime.now)
recipient = models.ForeignKey(User)
text = models.CharField(max_length=4096)
backup_calls = models.IntegerField(blank=True, null=True)
And a serializer for that model:
class CallSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='call-detail',
)
# some validation and custom field definitions
...
class Meta:
model = XCall
fields = ('url', 'id', 'text', 'recipient', 'send_on', 'backup_calls', 'status')
lookup_field= 'pk'
And here's the list view:
class CallList(generics.ListCreateAPIView):
serializer_class = CallSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrSuperuser,)
def pre_save(self, obj):
auth_user = self.request.user
obj.auth_user = auth_user
def get_queryset(self):
"""
This view should return a list of all the calls
for the currently authenticated user.
"""
auth = self.request.user
if isinstance(auth, AnonymousUser):
return []
elif auth.is_superuser:
return XCall.objects.all()
else:
return XCall.objects.filter(auth_user=auth)
In CallList's browseable API, I see the following in the POST form at the bottom:
My question is: why is there no default value set for send_on, and there is one for backup_calls? I assumed that the form would follow the XCall model specification and use datetime.now() for defaulting the former, and leave backup_calls blank (since it's nullable). How can I get the form to follow the model specifications?

You actually want to set an initial value, not a default value. See the docs. Your code should be:
from django.utils import timezone
class CallSerializer(serializers.HyperlinkedModelSerializer):
send_on = serializers.DateTimeField(initial=timezone.now())
...
A default value is the value provided to an attribute if no value is set for the field. The distinction between initial and default arguments mirrors the difference between Django's initial argument for form fields and Django's default argument for model fields.

There is a distinction between model defaults and initial values in forms. This is especially the case for default values which are actually functions because they are only called when the instance is saved. For example, which now do you want - this time at which the blank form is displayed, or the time at which the user presses "POST"? Django applies the default when saving the model if the field value is missing. To achieve what you want you need to manually set the default in the serialize field, for example:
class CallSerializer(serializers.HyperlinkedModelSerializer):
send_on = serializers.DateTimeField(default=datetime.now)
...

Related

How to seralize a MethodField with more than one object in Django?

I want to serialize a method that checks if the author of a story is the currently logged in user, if so returns true, if not false. However, the Docs of Django state that the Serliazer method requires only one argument besides self. So how can I access the user model in addition to the story (object)?
I was thinking about something like that:
class StorySerializer(serializers.ModelSerializer):
story_owner_permission = serializers.SerializerMethodField('check_story_owner_permission')
class Meta:
model = Story
fields = ['story_owner_permission']
def check_story_owner_permission(self, story, request):
story_author = story.author.id
current_user = request.user.id
if (story_author == current_user):
return True
else:
return False
But it doesn't work. check_story_owner_permission() missing 1 required positional argument: 'request'
You can use context in serializer, by default if you call your serializer from view, view have filled it automatically (see get_serializer_context):
class StorySerializer(serializers.ModelSerializer):
story_owner_permission = serializers.SerializerMethodField('check_story_owner_permission')
class Meta:
model = Story
fields = ['story_owner_permission']
def check_story_owner_permission(self, obj):
return obj.author.id == self.context["request"].user.id

Reusable admin form generator always checks last field

(This is all pseudocode and is not guaranteed to run.)
I am trying to make a "django admin form generator function" that outputs a django form. The current use case is to write reusable code that disallows admins from leaving a field empty, without also marking these fields as non-nullable.
So suppose there exists a model Foo, in which are some nullable fields:
class Foo(Model):
field1 = FloatField(null=True, blank=True, default=0.0)
field2 = FloatField(null=True, blank=True, default=0.0)
field3 = FloatField(null=True, blank=True, default=0.0)
and corresponding FooAdmin and FooForm, such that these fields cannot be made None from the admin.
class FooAdmin(ModelAdmin):
class FooForm(ModelForm):
class Meta(object):
model = Foo
fields = '__all__'
def _ensure_no_blanks(self, field):
value = self.cleaned_data.get(field)
if value is None:
raise forms.ValidationError(_('This field is required.'))
return value
# repeat methods for every field to check
def clean_field1(self):
return self._ensure_no_blanks('field1')
def clean_field2(self):
return self._ensure_no_blanks('field2')
def clean_field3(self):
return self._ensure_no_blanks('field3')
form = FooForm
As you can see, having to write clean_field1, clean_field2, and clean_field_n are repetitive and error-prone, so I write this helper function to generate model admins:
import functools
from django import forms
def form_with_fields(model_class, required_fields):
class CustomForm(forms.ModelForm):
class Meta(object):
model = model_class
fields = '__all__'
def ensure_no_blanks(self, field):
print field
value = self.cleaned_data.get(field)
if value is None:
raise forms.ValidationError('This field is required.')
return value
# make a clean_bar method for every field that I need to check for None
for field_name in required_fields:
handler = functools.partial(CustomForm.ensure_no_blanks, field=field_name)
setattr(CustomForm, 'clean_' + field_name, lambda self: handler(self))
return CustomForm
class CustomAdmin(ModelAdmin):
form = form_with_fields(Foo, ['field1', 'field2', 'field3'])
However, if you run such an admin, and if you do try to save the Foo model through the admin, you will see print field printing field3 three times in the terminal (i.e. all partials are retaining the last-run value).
Other attempts include overriding CustomForm's __getattr__(), and wrapping CustomForm in a type('Form', (CustomForm,), ..., which also exhibit the same behavior.
Is there a dry way to achieve this?
setattr(CustomForm, 'clean_' + field_name, lambda self: handler(self))
The problem is the lambda function. The handler is defined outside of the lambda. It is accessed when the lambda is called, not when it is defined. Since this is after the for-loop has completed, you always get the function that uses the last field name.
See this FAQ entry from the Python docs for a fuller explanation.
In this case, you don't need a lambda at all. Just use handler itself.
setattr(CustomForm, 'clean_' + field_name, handler)
However, as Paulo suggests in the comment, it would be much
def clean(self):
for field in required_fields(self):
self.ensure_no_blanks(field)
You may need to change ensure_no_blanks to use add_error so that the validation errors are added to the correct field.
if value is None:
self.add_error(field, 'This field is required.')
Another option would be to set the required=True for the fields in the __init__ method, then let the form take care of validation.
class CustomForm(forms.ModelForm):
class Meta(object):
model = model_class
fields = '__all__'
def __init__(self, *args, **kwargs):
super(CustomForm, self).__init__(*args, **kwargs)
for field in required_fields:
self.fields[field].required = True

django get default model field value

I want to write a view to reset some model field values to their default. How can I get default model field values?
class Foo(models.Model):
bar_field = models.CharField(blank=True, default='bar')
so what I want is:
def reset(request, id):
obj = get_object_or_404(Foo, id=id)
obj.bar_field = # logic to get default from model field
obj.save()
...
Since Django 1.10: myfield = Foo._meta.get_field('bar_field').get_default()
see here (not explained in the Docs apparently...) : https://github.com/django/django/blob/master/django/db/models/fields/init.py#L2301
myfield = Foo._meta.get_field_by_name('bar_field')
and the default is just an attribute of the field:
myfield.default

How do I represent two models on one page of form wizard?

I'm struggling to figure out how best to approach this.
I have two models that need to be represented on one page within a form wizard:
class BookingItem(models.Model):
assignedChildren = models.ManyToManyField('PlatformUserChildren', related_name = "childIDs", null=True)
quantity = models.PositiveSmallIntegerField(max_length=2,blank=True, null=True)
class PlatformUserChildren(models.Model):
child_firstname = models.CharField('Childs first name', max_length=30,blank=True, null=True)
The relationship between the models when presenting them on the page is NOT one-to-one. It is governed by quantity attribute and therefore there may be more BookingItems than PlatformUserChildren objects presented on the page (e.g. 4 BookingItems and 2 PlatformUserChildren objects). I loop through each object multiple times based on quantity.
I also need to bind to a queryset of the PlatformChildUser model based on the current logged in user.
My question: how do I best present these two models on the first page of my form wizard?
I have looked at inline_formsets, but they rely on foreign key relationships only.
I have tried a modelformset_factory for one model, and an additional identifier for the other model with some backend reconciliation later, but I'm stuck on how to get a queryset based on user in there
i have attempted the get_form_instance method but I'm not 100% sure if it supports querysets
finally, I have attempted overloading init however most of the examples are not based on form wizard, and supply external arguments.
My current (rather vanilla) code is below:
forms.py
class checkout_PlatformUserChildren(forms.ModelForm):
#activity_id = forms.IntegerField()
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
class Meta:
model = PlatformUserChildren
fields = ('child_age','child_firstname')
widgets = {
'child_firstname': SelectMultiple(attrs={'class': 'form-control',}),
'child_age' : TextInput(attrs={'class': 'form-control',}),
}
checkout_PlatformUserChildrenFormSet = modelformset_factory(
PlatformUserChildren,
form = checkout_PlatformUserChildren,
fields=('child_firstname', 'child_age'),
extra=1, max_num=5, can_delete=True)
views.py (done method not shown)
note: getUser is an external function that is currently working
checkoutForms = [
("assign_child", checkout_PlatformUserChildrenFormSet),
("address_information", addressInfo),
]
checkoutTemplates = {
"assign_child": "checkout/assign_child.html",
"address_information": "checkout/address_information.html",
}
class checkout(SessionWizardView):
def get_form_instance(self, step):
currentUser = getUser(self.request.user.id)
if step == 'assign_child':
self.instance = currentUser
return self.instance

default value for ChoiceField in modelform

I have problem with django:
models.py:
SUSPEND_TIME = (
('0', '0'),
('10', '10'),
('15', '15'),
('20', '20'),
class Order(models.Model):
user = models.ForeignKey(User)
city = models.CharField(max_length=20)
...
processed = models.BooleanField(default=False)
suspend_time = models.CharField(max_length=2, choices=SUSPEND_TIME, default='0')
..
form.py:
class OrderForm(forms.ModelForm):
class Meta:
model = Order
fields = ('suspend_time', 'processed')
view.py:
try:
order = Order.objects.get(id=order_id)
except Order.DoesNotExist:
order = None
else:
form = OrderForm(request.POST, instance=order)
if form.is_valid():
form.save()
....
then I send ajax request to update instance with only "processed" param..
form.is_valid is always False if I don't send "suspend_time" !
if request contain {'suspend_time': 'some_value' ...} form.is_valid is True
I don't understand why ? suspend_time has default value.. and order.suspend_time always has some value: default or other from choices.
why after form = OrderForm(request.POST, instance=order) form['suspend_time'].value() is None, other fields (city, processed) has normal value .
The behavior is as expected. The form should validate with given data. i.e. Whatever required fields are defined in the form, should be present in the data dictionary to instantiate it.
It will not use data from instance to populate fields that are not provided in form data.
Text from django model forms
If you’re building a database-driven app, chances are you’ll have forms that map closely to Django models. For instance, you might have a BlogComment model, and you want to create a form that lets people submit comments. In this case, it would be redundant to define the field types in your form, because you’ve already defined the fields in your model.
For this reason, Django provides a helper class that let you create a Form class from a Django model.