Dealing with additional kwargs in Django models - django

I want to pass some parameters with kwargs to get_or_create() function in Django models but if there are some additional key-values in kwargs that do not exist in models, an error will apear. Is there any way to pass the dictionary and the function itself handles the additional keys?

I think in Django source code you can find a better solution but, you also can create a function to remove extra keys from your data.
def remove_extra_keys(model, data: dict) -> dict:
for filed_name in data: # <- possibly for better reading u can use here data.keys()
if not getattr(model, filed_name, None):
data.pop(filed_name)
return data
p = MyModel.objects.get_or_create(**remove_extra_keys(model=MyModel, data=my_data))

Related

Django/Python convert string to Model filter with '=' in result

I'm working on writing test_templates so that I can very quickly write my tests, as I realized I was duplicating the same code with different variables. But I've run into a problem:
# path of view
# '/app/view/path/'
view_name = 'service:create_employee_profile'
# valid field values to test form success.
valid_values = {
'first_name': 'First',
'last_name': 'Last',
}
# Search criteria for Model 'get' and 'filter'
# Model.objects.get(field=value)
# Model.objects.get(eval(model_criteria))
model_criteria = 'first_name="First"'
"""
TESTS: Submitting forms
"""
# TEST: View saves valid object.
def test_view_saves_valid_object(self):
response = self.client.post(
reverse(view_name), valid_values)
self.assertTrue(Model.objects.filter(eval(model_criteria)).exists())
I thought I was set with eval(), until I quickly discovered that it doesn't like =. I tried using 2 different variables for 'first_name="First"', but a Model will never find a field out of a variable='field_name'.
These templates help me test multiple views with adding just a little information to them, and since more than 1 test in the template requires retrieving an instance of the model I am trying to set a variable at the top that will run all associated tests.
You can use a dictionary instead:
model_criteria = {'first_name': "First"}
Just unpack it when you pass it as filter() argument using **:
self.assertTrue(Model.objects.filter(**model_criteria).exists())

Django: How can I set initial values to formset's empty_form?

I just want to know how can I set initial values to empty_form.
I do create the Inlines with initial values for extra forms without problem, but, when user clicks to Add button, the fields I expect it have the initial values show up empty, and I hope it have the same initial values than extra forms.
How could I make the empty_form to be filled with initial data?
Thanks in advance.
Django doesn't really provide a way to set initial values for empty forms. I've found a couple ways to work around this:
Set the field values dynamically in javascript.
Overwrite the empty_form property for your formset.
example:
formset = formset_factory(MyClass, **kwargs)
empty = formset.empty_form
# empty is a form instance, so you can do whatever you want to it
my_empty_form_init(empty_form)
formset.empty_form = empty_form
I had a similar problem and what finally worked for me was using Django Dynamic Formset. What DDF does is instead of using the empty form to create the new formset, it uses one of the extra_forms as a template. The default behavior is to clear all field values from the extra_form before inserting the HTML to the DOM, but you can use the keepFieldValues setting to specify the ones you want to keep.
In my case I wanted to keep all hidden field values:
$(function() {
$('#myForm_table tbody tr').formset({
keepFieldValues: 'input:hidden',
}
});
});
Of course you can bypass Django Dynamic Formsets and implement your own add/delete code with Javascript if you prefer.
Accepted answer didn't work for me, hopefully this will help someone in the future, this is my solution:
Create a new class based on BaseInlineFormSet
Override empty_form
Create a FormSet with inlineformset_factory(formset=YourBaseInlineFormSet)
Create a formset instance and pass parameters to initial on the formset instance
Add the field on the HTML as usual
I used BaseInlineFormSet, but probably will work with other types of FormSet
verification is the name of the field for my example.
forms.py
class YourBaseInlineFormSet(forms.BaseInlineFormSet):
#property
def empty_form(self): # This is almost the same as Django 3.1 code
form = self.form(
auto_id=self.auto_id,
prefix=self.add_prefix("__prefix__"),
empty_permitted=True,
use_required_attribute=False,
initial={"verification": self.initial_extra[0]["verification"]}, # This is the extra parameter
**self.get_form_kwargs(None),
)
self.add_fields(form, None)
return form
YourFormSet = forms.inlineformset_factory(
SomeObject,
SomeRelatedObject,
fields="__all__",
widgets={"verification": forms.HiddenInput},
formset=YourBaseInlineFormSet,
)
views.py
from .forms import YourFormSet
def your_view(request):
formset = YourFormSet(
data=request.POST or None,
instance=object,
queryset=object.related_objects.all()
initial=[{"verification": verification} for a in range(FormSet().total_form_count())],
)
return render(request, template, context={'formset': formset})
template.html
<div id="empty_form" style="display:none">
{{ formset.empty_form }}
</div>
Working on Django 3.1
There is at least one way to do this: Specify the default value on your model Field.
Of course, this may have side effects, depending on your implementation of the model.
As #jkk-jonah mentioned, BaseFormSet does not provide a way to set initial values in the empty_form. However, a small change can provide a simple solution.
The following provides a way to supply the FormSet instance with empty initial values without disrupting its base behavior.
from django.forms.formsets import BaseFormSet
class FormSetWithDefaultEmptyFormInitials(BaseFormSet):
"""This formset enables you to set the initial values in ``empty_form``.
Usage: ``formset_factory(..., formset=FormSetWithDefaultEmptyFormInitials)``
"""
def __init__(self, *args, **kwargs):
if 'empty_initial' in kwargs:
self._empty_initial = kwargs.pop('empty_initial')
super().__init__(*args, **kwargs)
def get_form_kwargs(self, index):
"""Augmented to return the empty initial data
when the index is ``None``,
which is the case when creating ``empty_form``.
"""
if index is None:
kwargs = self.form_kwargs.copy()
if self._empty_initial:
# Assign the initial value passed to the Form class.
kwargs['initial'] = self._empty_initial
else:
kwargs = super().get_form_kwargs(index)
return kwargs
Then to use this you'd do something like:
NonEmptyFormSet = formset_factory(
BringYourOwnForm,
min_num=1,
extra=1,
formset=FormSetWithDefaultEmptyFormInitials,
)
# Let's say your form has name and address fields...
empty_form_initial_values = {'name': 'default name', 'address': 'default address'}
formset = NonEmptyFormSet(empty_initial=empty_form_initial_values)
asset formset.empty_form.initial == empty_form_initial_values
In my implementation empty_form is used to provide a template for frontend javascript to add additional forms to the formset. Thus, this allows me to set the initial values for that all of the forms in that formset.
Note, this does not take the place of initial values to the minimum number of forms within the formset (e.g. formset_factory(min_num=2, ...)). Therefore, it is necessary to assign those through the standard initial keyword argument.
Tested with Django 3.2.
See also the standard implementation of get_form_kwargs.
This partially extends the answer given by #RobertPro. Or at least, I used their answer as the stepping stone to my own solution.

How to serialize cleaned_data if it contains models?

I'm trying to serialize some form data so that I can stuff it into a hidden field until the user is ready to submit the whole form (think of a wizard).
I'm trying this:
print simplejson.dumps(vehicle_form.cleaned_data)
But I keep getting errors like this:
<VehicleMake: Honda> is not JSON serializable
Really I just need it to output the PK for "Honda".
This doesn't work either:
print serializers.serialize('json', vehicle_form.cleaned_data)
Gives:
'str' object has no attribute '_meta'
Presumably because it's iterating over the keys, which are all strings, whereas I think it expects a queryset, which I don't have.
So how do I do this?
Okay, so far I've come up with this:
from django.utils.simplejson import JSONEncoder, dumps, loads
from django.utils.functional import curry
from django.db.models import Model
from django.db.models.query import QuerySet
from django.core.serializers import serialize
class DjangoJSONEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Model):
return obj.pk
elif isinstance(obj, QuerySet):
return loads(serialize('json', obj, ensure_ascii=False))
return JSONEncoder.default(self, obj)
json_encode = curry(dumps, cls=DjangoJSONEncoder)
json_decode = loads
Based on the answers I found [here][1]. Now I'm trying this:
json = json_encode(vehicle_form.cleaned_data)
data = json_decode(json)
vehicle = Vehicle(**data)
The first 2 lines work perfectly, but the 3rd results in an exception:
Cannot assign "3": "Vehicle.model" must be a "VehicleModel" instance.
Getting close! Not sure how to deal with this one though...
This is a bit of a hack, but I don't know of a better way:
try:
vehicle_data = simplejson.loads(request.POST['vehicle_data'])
except ValueError:
vehicle_data = []
vehicle_data.append(vehicle_form.raw_data())
request.POST['vehicle_data'] = simplejson.dumps(vehicle_data)
It grabs the JSON data from hidden field in your form and decodes it into a Python dict. If it doesn't exist, it starts a new list. Then it appends the new raw/uncleaned data and re-encodes it and dumps it into the hidden field.
For this to work you need to either make a copy of the POST data (request.POST.copy()) so that it becomes mutable, or hack it like I did: request.POST._mutable = True
In my form template I put this:
<input type="hidden" name="vehicle_data" value="{{request.POST.vehicle_data}}" />
And lastly, to access the raw data for a form I added these methods:
from django.forms import *
def _raw_data(self):
return dict((k,self.data[self.add_prefix(k)]) for k in self.fields.iterkeys())
def _raw_value(self, key, value=None):
if value is None:
return self.data[self.add_prefix(key)]
self.data[self.add_prefix(key)] = value
Form.raw_data = _raw_data
Form.raw_value = _raw_value
ModelForm.raw_data = _raw_data
ModelForm.raw_value = _raw_value
Since .data returns too much data (in fact, it just returns the POST data you initially passed in), plus it's got the prefixes, which I didn't want.

Help understanding a Django view

I am trying to follow the code listed on https://github.com/alex/django-ajax-validation/blob/master/ajax_validation/views.py
I have been able to understand a small chunk of it. I have added comments stating my understanding of what is happening.
I would really appreciate some assistance on questions I listed in comments next to the lines I couldn't quite follow.
def validate(request, *args, **kwargs):
# I thing it is some sort of initializations but I cannot really understand what's happening
form_class = kwargs.pop('form_class')
defaults = {
'data': request.POST
}
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
kwargs = extra_args_func(request, *args, **kwargs)
defaults.update(kwargs)
form = form_class(**defaults)
if form.is_valid(): #straightforward, if there is no error then the form is valid
data = {
'valid': True,
}
else:
# if we're dealing with a FormSet then walk over .forms to populate errors and formfields
if isinstance(form, BaseFormSet): #I cannot really understand what is BaseFromSet
errors = {}
formfields = {}
for f in form.forms: # I am guessing that this is for when there are multiple form submitted for validation
for field in f.fields.keys(): # I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
formfields[f.add_prefix(field)] = f[field]
for field, error in f.errors.iteritems():
errors[f.add_prefix(field)] = error
if form.non_form_errors():
errors['__all__'] = form.non_form_errors() # what is the '__all__'?
else:
errors = form.errors
formfields = dict([(fieldname, form[fieldname]) for fieldname in form.fields.keys()])
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'): # I am having a hard time understanding what this if statement does.
fields = request.POST.getlist('fields') + ['__all__']
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
final_errors = {} # here the author of this code totally lost me.
for key, val in errors.iteritems():
if '__all__' in key:
final_errors[key] = val
elif not isinstance(formfields[key].field, forms.FileField):
html_id = formfields[key].field.widget.attrs.get('id') or formfields[key].auto_id
html_id = formfields[key].field.widget.id_for_label(html_id)
final_errors[html_id] = val
data = {
'valid': False or not final_errors,
'errors': final_errors,
}
json_serializer = LazyEncoder() # Why does the result have to be returned in json?
return HttpResponse(json_serializer.encode(data), mimetype='application/json')
validate = require_POST(validate) # a decorator that requires a post to submit
LazyEncoder
class LazyEncoder(JSONEncoder):
def default(self, obj):
if isinstance(obj, Promise):
return force_unicode(obj)
return obj
form_class = kwargs.pop('form_class')
This is simply pulling the keyword argument, form_class, that was passed in via the URL conf.
(r'^SOME/URL/$', 'ajax_validation.views.validate',
{'form_class': ContactForm}, # this keyword argument.
'contact_form_validate')
BaseFormSet is simply the formset class doing the work behind the scenes. When you don't know, search the source! grep -ri "baseformset" . It's an invaluable tool.
Take a look at at django.forms.formsets to see how formset_factory produces new "formset" classes based on the BaseFormSet, hence the factory part!
I am guessing that this is for when there are multiple form submitted for validation
Yes, that's exactly what a formset is for (dealing with multiple forms)
I think he is looping over all fields and checking for error. what does add_prefix () return? and what is formfields[]?
Yes, that would be looping through the field names.
add_prefix() is for prefixing form field names with a specific form. Because a formset repeats form elements multiple times, each field needs a unique prefix, such as 0-field1, 1-field1, etc.
formfields is just an empty dictionary defined a few lines above.
what is the 'all'?
__all__ is defined at the top of django.forms.forms
NON_FIELD_ERRORS = '__all__'
It's just what non field specific errors (such as constraints across 2 fields) are stored under in the errors dictionary as opposed to errors[fieldname].
I am having a hard time understanding what this if statement does.
The author has left a note:
# if fields have been specified then restrict the error list
if request.POST.getlist('fields'):
It's checking if you specified any specific fields to validate in your URLConf, this is not django but ajax_validation.
You can see that he's overwriting his errors dictionary based on only the fields specified, thus passing on the validation only for those fields.
errors = dict([(key, val) for key, val in errors.iteritems() if key in fields])
here the author of this code totally lost me.
The author has mapped a custom errors and fields dictionary to specific field names with prefixes, (as opposed to the usual FormSet with each form having its own errors dictionary, unaware of the formset itself) which he presumably uses in the AJAX response to validate all fields.
Normally, you can iterate over a formset and go through the errors on a form by form basis, but not so if you need to validate all of them through ajax.
The line pulling html_id should be straight forward most of the time, but it's there because form widgets CAN add interesting things to the end of the ID's based on whether or not the widget is a radio select for example.
From source comments :
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
Why does the result have to be returned in json?
Because it's an ajax request and javascript easily eats json.
2- could you go through these lines of code...
extra_args_func = kwargs.pop('callback', lambda request, *args, **kwargs: {})
Either return a keyword argument named 'callback' (which if passed in, is supposed to be a function that accepts request and return a dictionary), and if it wasn't, return a lambda function that only returns an empty dictionary.
I'm not sure what the specific use is for the extra context. You could use it to run arbitrary snippets of code without modifying or subclassing ajax_validation...
It might help you to run this code, and put a debugger breakpoint in somewhere so you can step through and examine the variables and methods. You can do this by simply putting this line where you want to break:
import pdb; pdb.set_trace()
and you will be dumped into the debugger in the console.

How to write a request filter / preprocessor in Django

I am writing an application in Django, which uses [year]/[month]/[title-text] in the url to identitfy news items. To manage the items I have defined a number of urls, each starting with the above prefix.
urlpatterns = patterns('msite.views',
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/edit/$', 'edit'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/$', 'show'),
(r'^(?P<year>[\d]{4})/(?P<month>[\d]{1,2})/(?P<slug>[\w]+)/save$', 'save'),
)
I was wondering, if there is a mechanism in Django, which allows me to preprocess a given request to the views edit, show and save. It could parse the parameters e.g. year=2010, month=11, slug='this-is-a-title' and extract a model object out of them.
The benefit would be, that I could define my views as
def show(news_item):
'''does some stuff with the news item, doesn't have to care
about how to extract the item from request data'''
...
instead of
def show(year, month, slug):
'''extract the model instance manually inside this method'''
...
What is the Django way of solving this?
Or in a more generic way, is there some mechanism to implement request filters / preprocessors such as in JavaEE and Ruby on Rails?
You need date based generic views and create/update/delete generic views maybe?
One way of doing this is to write a custom decorator. I tested this in one of my projects and it worked.
First, a custom decorator. This one will have to accept other arguments beside the function, so we declare another decorator to make it so.
decorator_with_arguments = lambda decorator: lambda * args, **kwargs: lambda func: decorator(func, *args, **kwargs)
Now the actual decorator:
#decorator_with_arguments
def parse_args_and_create_instance(function, klass, attr_names):
def _function(request, *args, **kwargs):
model_attributes_and_values = dict()
for name in attr_names:
value = kwargs.get(name, None)
if value: model_attributes_and_values[name] = value
model_instance = klass.objects.get(**model_attributes_and_values)
return function(model_instance)
return _function
This decorator expects two additional arguments besides the function it is decorating. These are respectively the model class for which the instance is to be prepared and injected and the names of the attributes to be used to prepare the instance. In this case the decorator uses the attributes to get the instance from the database.
And now, a "generic" view making use of a show function.
def show(model_instance):
return HttpResponse(model_instance.some_attribute)
show_order = parse_args_and_create_instance(Order, ['order_id'])(show)
And another:
show_customer = parse_args_and_create_instance(Customer, ['id'])(show)
In order for this to work the URL configuration parameters must contain the same key words as the attributes. Of course you can customize this by tweaking the decorator.
# urls.py
...
url(r'^order/(?P<order_id>\d+)/$', 'show_order', {}, name = 'show_order'),
url(r'^customer/(?P<id>\d+)/$', 'show_customer', {}, name = 'show_customer'),
...
Update
As #rebus correctly pointed out you also need to investigate Django's generic views.
Django is python after all, so you can easily do this:
def get_item(*args, **kwargs):
year = kwargs['year']
month = kwargs['month']
slug = kwargs['slug']
# return item based on year, month, slug...
def show(request, *args, **kwargs):
item = get_item(request, *args, **kwargs)
# rest of your logic using item
# return HttpResponse...