get_or_create for two related models and formsets - django

I have two models related by a ForeignKey and inline formsets:
class Balanta(models.Model):
data = models.DateField()
class Meta:
ordering=['data']
verbose_name_plural="Balante"
def __unicode__(self):
return unicode(self.data)
class Conturi(models.Model):
cont=models.PositiveIntegerField()
cont_debit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
cont_credit=models.DecimalField(default=0, max_digits=30, decimal_places=2)
balanta = models.ForeignKey(Balanta)
class Meta:
#oredering=['cont']
verbose_name_plural="Conturi"
def __unicode__(self):
return unicode(self.cont)
I have formsets based on the two models. After i submit the form, if the "Balanta part of the form" exists, the form shouldn't do anything an dif it doesn't exists it should save it in the database (I managed to do this).
Now what i want to do on the "Conturi part of the form" is to see if the cont is on the database, the form should update it with the value in the input box and if it is not in the db, the form should create it and the corresponding cont_debit or cont_credit. The current state gives me: get_or_create() argument after ** must be a mapping, not list.
I think is something bigger then mt simple/newbie way to solve the problem.
Here is the view:
*LAST UPDATE*
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from sitfin.models import Balanta, Conturi
from sitfin.forms import BalantaForm , ConturiForm
from django.forms.models import inlineformset_factory
def balanta_introducere(request):
balanta=Balanta()
ConturiInlineFormSet=inlineformset_factory(Balanta, Conturi, extra=3)
if request.method=='POST':
balanta_form=BalantaForm(request.POST, instance=balanta)
if balanta_form.is_valid():
balanta, created=Balanta.objects.get_or_create(**balanta_form.cleaned_data)
#return HttpResponseRedirect('/sitfin/balantaok')
formset=ConturiInlineFormSet(request.POST, request.FILES, instance=balanta)
if formset.is_valid():
#formset.save()
for form in formset:
data={
'cont':form.cleaned_data.get('cont'),
'cont_debit':form.cleaned_data.get('cont_debit'),
'cont_credit':form.cleaned_data.get('cont_credit'),
'balanta':form.cleaned_data.get('balanta'),
}
try:
c=Conturi()#.objects.get(cont=data['cont'])
except Conturi.DoesNotExist:
cont_complete,created=Conturi.objects.get_or_create(**data)
else:
for form in formset:
new_data={
'cont':form.cleaned_data.get('cont'),
'cont_debit':form.cleaned_data.get('cont_debit'),
'cont_credit':form.cleaned_data.get('cont_credit'),
'balanta':form.cleaned_data.get('balanta'),
}
cont_complete,created=Conturi.objects.get_or_create(**new_data)
else:
balanta_form=BalantaForm()
formset=ConturiInlineFormSet(instance=balanta)
return render_to_response('sitfin/balanta_introducere.html',{'balanta_form':balanta_form,'formset':formset}, context_instance=RequestContext(request))

formset.cleaned_data is a list of the individual forms cleaned_data. You need to iterate over formset and create a Conturi object for each:
if formset.is_valid():
for form in formset:
cont, created=Conturi.objects.get_or_create(**form.cleaned_data)
UPDATE:
To protect yourself from errors resulting from additional form fields that do not correspond to model attributes, it's better to explicitly provide the arguments to get_or_create instead of using **form.cleaned_data.
if formset.is_valid():
for form in formset:
data = {
'cont': form.cleaned_data.get('cont'),
'cont_debit': form.cleaned_data.get('cont_debit'),
'cont_credit': form.cleaned_data.get('cont_credit'),
'balanta': form.cleaned_data.get('balanta'),
}
cont, created = Conturi.objects.get_or_create(**data)
It's basically the same procedure, but now you know exactly which arguments are being passed to get_or_create.

Related

Modelformset extra field pk is rendered as next instance in Django

I don't get why my extra fields in Modelformset are rendered with next instance pk, when form where I have already saved data is showing relevant related model instance pk.
Is this something why Django suggests to render pk field explicitly in the templates, so user to choose right related model pk? looks to me unbelievable, so please help me here...
# My views.py
from django.forms import modelformset_factory
from .models import PreImplement
# Pre Implement View
def pre_implement_view(request, pk):
moc = get_object_or_404(Moc, pk=pk)
print(moc.pk)
PreImplementFormSet = modelformset_factory(PreImplement, fields=('__all__'), can_delete=True, can_delete_extra=False)
formset = PreImplementFormSet(queryset=PreImplement.objects.filter(moc_id=moc.pk),
initial=[
{'action_item': 'go'},
{'action_item': 'nogo'},
{'action_item': 'na'},
])
formset.extra=3
queryset=PreImplement.objects.filter(moc_id=moc.pk)
print('babr', queryset)
if request.method == 'POST':
formset = PreImplementFormSet(request.POST, initial=[{'action_item': 'babrusito'},
{'action_item': 'go'},
{'action_item': 'nogo'},
{'action_item': 'na'},
])
if formset.is_valid():
formset.save()
return redirect('index')
return render(request, 'moc/moc_content_pre_implement.html', context={'moc': moc, 'formset': formset})
This is continuation of my previous SO post struggles:
Populate model instance with another model data during instance creation
Here is snapshot from my template:

Ensure 2 choice fields on a record are not equal in Django

I have a model with 2 CharFields selected from nearly the same list of choices. They are the same except for the 2nd inclination having a "None" option.
class Pawn(models.Model):
primary_inclination = models.CharField(max_length=30, choices=PRIMARY_INCLINATION_CHOICES)
secondary_inclination= models.CharField(max_length=30, choices=SECONDARY_INCLINATION_CHOICES)
I want to ensure the same value can not be selected for both fields. For example, if my choices are selected from A, B, C, then A and B is fine, but A and A is not. It is ok for another Pawn to also have A and B.
Some things I've looked into:
Specifying unique fields in Meta, but this makes the inclinations only selectable by 1 Pawn
unique_together, no other pawn can have the same values for the two fields
You can use the clean method in your model to add custom validation and raise a ValidationError if the fields are the same:
from django.core.exceptions import ValidationError
class Pawn(models.Model):
primary_inclination = models.CharField(max_length=30, choices=PRIMARY_INCLINATION_CHOICES)
secondary_inclination= models.CharField(max_length=30, choices=SECONDARY_INCLINATION_CHOICES)
def clean(self):
if self.primary_inclination == self.secondary_inclination:
raise ValidationError('Primary and secondary inclinations should be different.')
It's a good idea to validate as close to the source as possible. Implementing methods such as clean or save_model (within Django Admin) validates at the server level which is the last line of defense. You can also use JavaScript to keep your form's submit button disabled unless the selected inclinations are different. Another technique is to use form validation in the view:
from django.core.exceptions import ValidationError
from django import forms
class PawnForm(forms.Form):
primary_inclination = forms.CharField()
secondary_inclination = forms.CharField()
def clean(self):
# Assume posted data includes pinc (primary_inclination) and sinc (secondary inclination)
if 'pinc' in self.cleaned_data and 'sinc' in self.cleaned_data:
if self.cleaned_data['pinc'] == self.cleaned_data['sinc']:
raise forms.ValidationError(
("Primary and Secondary inclinations must be different"))
else:
raise forms.ValidationError(
("Both Primary and Secondary inclinations required"))
return self.cleaned_data
In your view you could have something like:
from django.shortcuts import render, redirect
from django.urls import reverse
from django.views.generic.edit import FormView
class PawnView(FormView):
def get(self, request):
context = {}
context['form'] = PawnForm(request.POST or None)
return render(request, 'pawn.html', context)
def post(self, request):
context = {}
form = PawnForm(request.POST or None)
context['form'] = form
if form.is_valid():
# Form is valid...go ahead and process data
new_pawn = Pawn.create(form.cleaned_data.get(‘pinc’), form.cleaned_data.get(‘sinc’)
return redirect(reverse('pawn-view'))
else:
for k, v in form.errors.items():
# Retrieve the text of our validation error or you can simply use form.errors
context['error'] = v
return render(request, 'pawn.html', context)
You can show the validation error in pawn.html very easily:
<div class="pawn-error">
<h3>{{ error | striptags }}</h3>
</div>

how to pass the parameter to a form during formset creation

I have an inline formset in Django. And I wonder how can I set some max_value for a form that is in this formset based on the instance parameters. The problem is that when we create a formset by using inlineformset_factory we pass there not an instance of the form, but the class, so I do not know how to pass the parameters there.
when the form is initiated, there is already an instance object available in kwargs that are passed there. But for some strange reasons, when I try to set max_value nothing happens.
Right now I've found quite an ugly solution to set the widget max attribute. This one works but I wonder if there is a correct way of doing it.
from .models import SendReceive, Player
from django.forms import inlineformset_factory, BaseFormSet, BaseInlineFormSet
class SRForm(forms.ModelForm):
amount_sent = forms.IntegerField(label='How much to sent?',
required=True,
widget=forms.NumberInput(attrs={'min': 0, 'max': 20})
)
def __init__(self, *args, **kwargs):
super(SRForm, self).__init__(*args, **kwargs)
curmax = kwargs['instance'].sender.participant.vars['herd_size']
self.fields['amount_sent'].widget.attrs['max'] = int(curmax)
class Meta:
model = SendReceive
fields = ['amount_sent']
SRFormSet = inlineformset_factory(Player, SendReceive,
fk_name='sender',
can_delete=False,
extra=0,
form=SRForm,)
formset = SRFormSet(self.request.POST, instance=self.player)
You could use functools to create a curried version of your Form class:
from functools import partial, wraps
# create a curried form class
form_with_params = wraps(SRForm)(partial(SRForm, your_param='your_value'))
# use it instead of the original form class
SRFormSet = inlineformset_factory(
Player,
SendReceive,
# ...
form=form_with_params,
)

Django using form input to query data -- should be simple, isn't (for me)

I have a form which looks like this:
class AddressSearchForm(forms.Form):
"""
A form that allows a user to enter an address to be geocoded
"""
address = forms.CharField()
I'm not storing this value, however, I am geocoding the address and checking to make sure it is valid:
def clean_address(self):
address = self.cleaned_data["address"]
return geocode_address(address, True)
The geocode function looks like this:
def geocode_address(address, return_text = False):
""" returns GeoDjango Point object for given address
if return_text is true, it'll return a dictionary: {text, coord}
otherwise it returns {coord}
"""
g = geocoders.Google()
try:
#TODO: not really replace, geocode should use unicode strings
address = address.encode('ascii', 'replace')
text, (lat,lon) = g.geocode(address)
point = Point(lon,lat)
except (GQueryError):
raise forms.ValidationError('Please enter a valid address')
except (GeocoderResultError, GBadKeyError, GTooManyQueriesError):
raise forms.ValidationError('There was an error geocoding your address. Please try again')
except:
raise forms.ValidationError('An unknown error occured. Please try again')
if return_text:
address = {'text':text, 'coord':point}
else:
address = {'coord':point}
return address
What I now need to do is create a view that will query a model using the address data to filter the results. I'm having trouble figuring out how to do this. I'd like to use CBV's if possible. I can use FormView to display the form, and ListView to display the query results, but how do I pass the form data between the two?
Thanks in advance.
UPDATE: I know how to query my model to filter the results. I just don't know how to properly combine using a Form and Class Based Views so that I can access the cleaned_data for my filter. e.g:
Process should be:
1) Display form on get
2) Submit form and validate (geocode address) on post
3) Run query and display results
address = form.cleaned_data['address']
point = address['coord']
qs = model.objects.filter(point__distance_lte=(point, distance)
Ok, here's a generic version of what ended up working based on psjinx direction:
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
"""
A View which takes a queryset and filters it via a validated form submission
"""
queryset = {{ initial queryset }} # you can use a model here too eg model=foo
form_class = {{ form }}
def get(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
return self.render_to_response(self.get_context_data(form=form))
def post(self, request, *args, **kwargs):
form_class = self.get_form_class()
form = self.get_form(form_class)
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
queryset = self.get_queryset()
search_param = form.cleaned_data['{{ form field }}']
object_list = queryset.filter({{ filter operation }}=search_param)
context = self.get_context_data(object_list=object_list, form=form, search_param=search_param)
return self.render_to_response(context)
This is similar to a question asked here or I will say a combination of two questions.
Django: Search form in Class Based ListView
Search multiple fields of django model without 3rd party app
django simple approach to multi-field search (if your models similar to one mentioned in this question)
Please have a look at above questions and their answers and if you still have any question then reply in comment to this answer.
Update 1
from django.views.generic.base import TemplateResponseMixin, View
from django.views.generic.edit import FormMixin
from django.views.generic.list import MultipleObjectMixin
class SearchView(FormMixin, MultipleObjectMixin, TemplateResponseMixin, View):
model = SomeModel
form_class = AddressSearchForm
template = "search.html"
def get_queryset():
## Override it here
pass
def post():
## If your form is invalid then request won't reach here
## because FormMixin is in left most position
## do something
## call self.render_to_response()
def form_valid():
## Your form is valid do something
## if form is invalid then next method will be called
pass
def form_invalid(self):
## form is not valid
## render to some template
Useful Links:
https://github.com/django/django/blob/1.4.3/django/views/generic/base.py
https://github.com/django/django/blob/1.4.3/django/views/generic/edit.py
https://github.com/django/django/blob/1.4.3/django/views/generic/list.py
Related Question:
Django - Mixing ListView and CreateView

Django Form, Calculating a new value from the form and assign it to the model

I'm pretty new with Django and the whole web-developing concept. I've only taken Java and C++ , but I got a job working as a web-developer at my university. I'm currently trying to implement a form - (http://albedo.csrcdev.com/pages/submit). In my models, I have one more field that doesn't show up on my form, which is called Albedo. Albedo is supposed to be calculated by sum( outgoing1, outgoing2, outgoing3 ) / sum( incoming1, incoming2, incoming3 ). So my question is, how and where do I take those variables from the database, and assign the new calculated value to Albedo.
My co-worker told me to use ModelForm for my form, and try doing it in views.py but now I'm sitting here stuck and clueless and he just left for vacation! :(
Thanks in advance,
David
views.py
#login_requried
def submit( request ):
if request.method =='POST':
form = DataEntryForm( request.POST )
model = DataEntry( )
if form.is_valid():
form.save()
return HttpResponseRedirect('/map/rebuild/')
else:
form = DataEntryForm( )
return render_to_response(
'pages/submit.html', { 'form': form },
context_instance=RequestContext(request) )
form = DataEntryForm(request.POST)
if form.is_valid():
instance = form.save(commit=False)
instance.albedo = do_calc(instance.whatever0, instance.whatever1)
instance.save()
return HttpResponseRedirect('/map/rebuild/')
Note that you don't need to instantiate model = DataEntry() manually - if DataEntryForm is a ModelForm subclass, it'll create the model when you call .save().
It would probably be a good idea to encapsulate the calculation in a DataEntry.update_albedo() method or something. You would call that before instance.save() instead doing the calculation in the view itself.
I would assign this value from the save method of the ModelForm.
Assuming that albedo is a field name in your model, too:
Class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
exclude = ('albedo')
def calculate_albedo(outgoing1, outgoing2, outgoing3, incoming1,
incoming2, incoming3):
return sum([outgoing1, outgoing2, outgoing3]) / sum([incoming1,
incoming2, incoming3])
def save(self, commit=True):
form_data = self.cleaned_data
self.instance.someval1 = form_data['someval1']
self.instance.someval2 = form_data['someval2']
self.instance.someval3 = form_data['someval3']
self.instance.albedo = self.calculate_albedo(
form_data['outoing1'], form_data['outoing2'],
form_data['outoing3'], form_data['incoming1'],
form_data['incoming2'], form_data['incoming3'])
return super(MyModelForm, self).save(commit)
Also, if Albedo is not a class name you should use lowercase. It's Pythonic convention.
A possible solution is to update field in presave signal. With this approach:
Your Albedo field is updated before save model.
Remember you can do a save no commit save(commit=False). This will update your field without commit changes to database.
I post a sample taked from django signals doc:
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel
#receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
sender.Albedo = sender.outgoing1 + ...