I am trying to create a new table in my MySQL DB with a single field to record an incrementing value. However I keep getting an error:
Exception Value: global name 'total_max_counter' is not defined
Can someone tell me where I am going wrong?
views.py
from survey.models import TotalCounter
def done(self, form_list, **kwargs):
total_counter = TotalCounter.objects.get_or_create(total_max_counter)
total_counter.survey_wizard_total += 1
total_counter.save()
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
models.py
class TotalCounter(models.Model):
total_max_counter = models.SmallIntegerField(default=0)
def __unicode__(self):
return self
Firstly, in get_or_create(), you need to specify the argument in the form of kwargs. Then only, it will look up an object with the given kwargs, creating one if necessary. kwargs may be empty if your model has defaults for all fields.
The function definition for get_or_create() is:
get_or_create(defaults=None, **kwargs)
Secondly, you are trying to update the survey_wizard_total field of TotalCounter object whereas your model has no such field defined in it. It has only the field total_max_counter in it. You will also need to correct that in your code.
This should work for you:
from django.db.models import F
from survey.models import TotalCounter
def done(self, form_list, **kwargs):
total_counter = TotalCounter.objects.get_or_create(total_max_counter__gte=0)[0]
total_counter.total_max_counter = F('total_max_counter') + 1 # increment value of `total_max_counter` by 1
total_counter.save() # save the object
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
You are using get_or_create method in a wrong way:
total_counter = TotalCounter.objects.get_or_create(total_max_counter)
You are trying to get object from database but you are not making any comparison.
It should be something like:
total_counter = TotalCounter.objects.get_or_create(total_max_counter=<some value to match against>)
Even after this correction if your query runs, then an object for TotalCounter will be returned which have no attribute 'survey_wizard_total'
total_counter.survey_wizard_total += 1
Related
I created the FormView below that will dynamically return a form class based on what step in the process that the user is in. I'm having trouble with the get_form method. It returns the correct form class in a get request, but the post request isn't working.
tournament_form_dict = {
'1':TournamentCreationForm,
'2':TournamentDateForm,
'3':TournamentTimeForm,
'4':TournamentLocationForm,
'5':TournamentRestrictionForm,
'6':TournamentSectionForm,
'7':TournamentSectionRestrictionForm,
'8':TournamentSectionRoundForm,}
class CreateTournament(FormView):
template_name = 'events/create_tournament_step.html'
def __init__(self, *args, **kwargs):
form_class = self.get_form()
success_url = self.get_success_url()
super(CreateTournament, self).__init__(*args, **kwargs)
def get_form(self, **kwargs):
if 'step' not in kwargs:
step = '1'
else:
step = kwargs['step']
return tournament_form_dict[step]
def get_success_url(self, **kwargs):
if 'step' not in kwargs:
step = 1
else:
step = int(kwargs['step'])
step += 1
if 'record_id' not in kwargs:
record_id = 0
else:
record_id = int(kwargs['record_id'])
return 'events/tournaments/create/%d/%d/' % (record_id, step)
The post request fails at the django\views\generic\edit.py at the get_form line, which I realize is because I've overwritten it in my FormView:
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid(): …
return self.form_valid(form)
else:
return self.form_invalid(form)
However, when I change the name of my custom get_form method to say gen_form, like so:
def __init__(self, *args, **kwargs):
form_class = self.gen_form()
success_url = self.get_success_url()
super(CreateTournament, self).__init__(*args, **kwargs)
def gen_form(self, **kwargs):
if 'step' not in kwargs:
step = '1'
else:
step = kwargs['step']
return tournament_form_dict[step]
my form class doesn't get processed in the get request and evaluates to None. I'm scratching my head as to why when I override the get_form method, it works, but my own named method doesn't? Does anyone know what the flaw might be?
Django's FormMixin [Django-doc] defines a get_form function [Django-doc]. You here thus basically subclassed the FormView and "patched" the get_form method.
Your attempt with the gen_form does not work, since you only defined local variables, and thus do not make much difference anyway, only the super(..) call will have some side effects. The other commands will keep the CPU busy for some time, but at the end, will only assign a reference to a Form calls to the form_class variable, but since it is local, you will throw it away.
That being said, your function contains some errors. For example the **kwargs will usually contain at most one parameter: form_class. So the steps will not do much. You can access the URL parameters through self.args and self.kwargs, and the querystring parameters through self.request.GET. Furthermore you probably want to patch the get_form_class function anyway, since you return a reference to a class, not, as far as I understand it, a reference to an initilized form.
Constructing URLs through string processing is probably not a good idea either, since if you would (slightly) change the URL pattern, then it is likely you will forget to replace the success_url, and hence you will refer to a path that no longer exists. Using the reverse function is a safer way, since you pass the name of the view, and parameters, and then this function will "calculate" the correct URL. This is basically the mechanism behind the {% url ... %} template tag in Django templates.
A better approach is thus:
from django.urls import reverse
class CreateTournament(FormView):
template_name = 'events/create_tournament_step.html'
def get_form_class(self):
return tournament_form_dict[self.kwargs.get('step', '1')]
def get_success_url(self):
new_step = int(self.kwargs.get('step', 1)) + 1
# use a reverse
return reverse('name_of_view', kwargs={'step': new_step})
I've had to create a custom formset to validate the results of my formset. I've simplified the validation for the purpose of this example.
forms.py
class BaseUserServiceFormSet(BaseFormSet):
def get_form_kwargs(self, index):
kwargs = super().get_form_kwargs(index)
kwargs['queryset'] = index
return kwargs
def clean(self):
for form in self.forms:
user_title = form.cleaned_data['user_title']
if user_title == None:
self.add_error('user_title', "Please enter a title for this service.")
Within my views, I'm passing a queryset into the formset.
views.py
UserServiceFormSet = modelformset_factory(UserService, form=UserServiceForm, formset=BaseUserServiceFormSet, extra=1, max_num=4, can_delete=True)
formset = UserServiceFormSet(request.POST or None, request.FILES or None, queryset=UserService.objects.filter(user=user, title__extra_service=1), prefix='first')
When I open my page, I get the following error:
__init__() got an unexpected keyword argument 'queryset'
I've tried adding the following to my BaseUserServiceFormSet (in forms.py), but then I start to get a bunch of errors that refer to the default values that are initialized within the BaseFormSet (https://docs.djangoproject.com/en/2.1/_modules/django/forms/formsets/). In otherwords, with my code, I'm overwritting the default init of BaseFormSet, which breaks things.
def __init__(self, *args, **kwargs):
queryset = kwargs.pop('queryset')
What change would I have to make in order to pass my queryset into my formset as a kwarg?
Thank you!
Since you are using modelformset_factory, you should be subclassing BaseModelFormSet.
class BaseUserServiceFormSet(BaseModelFormSet):
...
You probably shouldn't do queryset = kwargs.pop('queryset') - that will prevent the queryset being passed if you call super().__init__(*args, **kwargs) later in the __init__ method.
The get_form_kwargs is used to pass kwargs to the form, which isn't what you're trying to do here.
I have created a small counter model class Counter to store the amount of times a small survey application has been completed.
I want to increment SurveyWizardOneCounter in my models.py from my def done() method in my views.py
Can anyone tell me how to do this?
Previously I was using Global variables for my counter which I have found out do not work properly with a web app. I am trying to learn how to create and store a counter in my DB. Any help is appreciated.
models.py
class Counter(models.Model):
SurveyWizardOneCounter = models.SmallIntegerField()
TotalMaxCounter = models.SmallIntegerField()
views.py
from survey.models import Counter
def done(self, form_list, **kwargs):
Counter.SurveyWizardOneCounter += 1 # This is where I am trying to increment the counter
logger.debug('\n\n SurveyWizardOneCounter = %s', SurveyWizardOneCounter)
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
Since you have 9 different survey counters and you need to increment each counter when a survey is submitted, it would be better if you define a field survey_wizard_type with its possible values being survey_wizard_one, survey_wizard_two till survey_wizard_nine and a field survey_wizard_count having default value 0 which stores the count for that particular survey wizard. There would be 9 records in your database for Counter model then.
models.py
SURVEY_WIZARD_TYPE_CHOICES = ['survey_wizard_one', 'survey_wizard_two', 'survey_wizard_three', 'survey_wizard_four', 'survey_wizard_five', 'survey_wizard_six', 'survey_wizard_seven', 'survey_wizard_eight', 'survey_wizard_nine']
class Counter(models.Model):
survey_wizard_type = models.CharField(choices=SURVEY_WIZARD_TYPE_CHOICES)
survey_wizard_count = models.SmallIntegerField(default=0)
total_max_counter = models.SmallIntegerField()
Then in your views.py, you can use get_or_create method for looking up an Counter object with the given survey_wizard_type, creating one if necessary. Then increment the survey_wizard_count by 1 and save that object into the database.
views.py
from django.db.models import F
from survey.models import Counter
def done(self, form_list, **kwargs):
survey_counter = Counter.objects.get_or_create(survey_wizard_type= 'survey_wizard_x') # x can be any value from one to nine
survey_counter.survey_wizard_count = F('survey_wizard_count') + 1
survey_counter.save()
logger.debug('\n\n SurveyWizardOneCounter = %s', SurveyWizardOneCounter)
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
EDIT
Rahul provided the solution but this is the code I ended up using
models.py
class Counter(models.Model):
SURVEY_WIZARD_TYPE_CHOICES = (
('SURVEY_WIZARD_ONE', 'survey_wizard_one'),
('SURVEY_WIZARD_TWO', 'survey_wizard_two'),
('SURVEY_WIZARD_THREE', 'survey_wizard_three'),
('SURVEY_WIZARD_FOUR', 'survey_wizard_four'),
('SURVEY_WIZARD_FIVE', 'survey_wizard_five'),
('SURVEY_WIZARD_SIX', 'survey_wizard_six'),
('SURVEY_WIZARD_SEVEN', 'survey_wizard_seven'),
('SURVEY_WIZARD_EIGHT', 'survey_wizard_eight'),
('SURVEY_WIZARD_NINE', 'survey_wizard_nine'),
)
survey_wizard_type = models.CharField(max_length=1000, choices=SURVEY_WIZARD_TYPE_CHOICES)
survey_wizard_count = models.SmallIntegerField(default=0)
total_max_counter = models.SmallIntegerField(default=0)
views.py
def done(self, form_list, **kwargs):
survey_counter = Counter.objects.get_or_create(survey_wizard_type= 'survey_wizard_one')[0] # x can be any value from one to nine
survey_counter.survey_wizard_count = F('survey_wizard_count') + 1
survey_counter.save()
for form in form_list:
form.save()
return render(self.request, 'Return_to_AMT.html', {
'form_data': [form.cleaned_data for form in form_list],
})
I will assume your Counter model will store just one record.
c = Counter.objects.get()
c.SurveyWizardOneCounter += 1
# Update another attributes if you need
c.save()
Or maybe
from django.db.models import F
Counter.objects.filter().update(SurveyWizardOneCounter=F('SurveyWizardOneCounter')+1)
You should make sure you use an atomic method to update the field, some of the answers above could have a race condition between update and save. The Django documentation suggests to use the following method:
from django.db.models import F
product = Product.objects.get(name='Venezuelan Beaver Cheese')
product.number_sold = F('number_sold') + 1
product.save()
I am newbie with Django and I get stucked trying to pass the value from a html table rendered with django-tables2 to a form.
view.py
def configView(request):
form = ConfigForm(request.POST or none)
if form.is_valid():
save_it = form.save(commit=False)
save_it.save()
Messages.success(request, 'Configuracion Actualizada')
return HttpResponseRedirect('/monitor/')
return render_to_response("config.html",
locals(),
context_instance=RequestContext(request))
This is my forms.py
class ConfigForm(forms.ModelForm):
class Meta:
model = Config
def __init__(self, *args, **kwargs):
super(ConfigForm, self).__init__(*args,**kwargs)
self.fields['id_proveedor'].initial = kwargs.pop('id_proveedor',None)
But I don't know how to retrieve and pass the value to theform.
I need pass the values from the cells 0, 2 and 6.
Any advice or snippet will be appreciated.
Thanks in advance
I would try this:
class ConfigForm(forms.Form):
def __init__(self, *args, **kwargs):
your_variable_to_pass = kwargs.pop("your_variable_to_pass")
super(ConfigForm, self).__init__(*args,**kwargs)
self.fields['id_proveedor']= forms.FieldClass(attribute=your_variable_to_pass)
id_proveedor = FieldClass()
where, 'FieldClass' is whatever field you choose (i.e. ChoiceField, CharField) and
attribute is the attribute to pass (your variable), i.e. 'choices', 'initial' etc.
thus, it may look like this:
self.fields['id_proveedor']= forms.ChoiceField(choices=your_variable_to_pass)
id_proveedor = ChoiceField()
Notice indentation - you assign value of the attribute to pass in the constructor!; in case of ChoiceField choices is a list of tuples, i.e. (('1', 'First',), ('2', 'Second',)); I use Forms instead of ModelForm as super or base class in this example
Then, in the views: f = ConfigFrom(request.POST, your_variable_to_pass=your_variable_to_pass)
notice your_variable_to_pass=your_variable_to_pass otherwise it'll generate a key error
I hope, it helps!
I am trying to edit existing objects using my Django FormWizard. I am following the technique described in this blog post, but it does not work. Here is my edit view:
#login_required
def edit_wizard(request, id):
thing = get_object_or_404(Thing, pk=id)
if thing.user != request.user:
raise HttpResponseForbidden()
else:
initial = {0: {'year': thing.year,
'make': thing.make,
'series': thing.series,
....etc.
},
1: {'condition': thing.condition,
....etc.
},
}
form = CreateWizard.as_view([StepOneForm, StepTwoForm, StepThreeForm], initial_dict=initial)
return form(context=RequestContext(request), request=request)
Can you help me figure out how to provide the initial data to the Wizard so that I can allow users to edit their objects? Thanks for your ideas!
EDIT: (2/18/13)
Was getting a:
TypeError at /edit/10/ __init__() takes exactly 1 argument (3 given)
This was solved by #sneawo's answer below, but still no initial data is passed, and the wizard instead creates new objects.
EDIT: (2/19/13)
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, form_list, **kwargs):
instance = Thing()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
As per the documentation, for Django 1.4+ you pass the initial data in initial_dict keyword argument. For previous versions(1.3, it seems it wasn't there before 1.3) the keyword argument was initial. Also, the keys for steps in your initial data dict should be strings not integers.
initial = {'0': {'year': thing.year,
'make': thing.make,
'series': thing.series,
....etc.
},
'1': {'condition': thing.condition,
....etc.
},
}
UPDATE:
To update the same object you have to set the id also, otherwise there is no way for django to know which object to update. A simple way to do it is to pass the id in a hidden field, but you have to do the user permission check again in your (done) method.
initial = {0: {'id': thing.id,
class CreateWizard(SessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT))
def done(self, form_list, **kwargs):
id = form_list[0].cleaned_data['id']
thing = get_object_or_404(Thing, pk=id)
if thing.user != self.request.user:
raise HttpResponseForbidden()
else:
instance = Thing()
for form in form_list:
for field, value in form.cleaned_data.iteritems():
setattr(instance, field, value)
instance.user = self.request.user
instance.save()
return render_to_response('wizard-done.html', {
'form_data': [form.cleaned_data for form in form_list],})
and of course StepOneForm should have id with hidden field:
class StepOneForm(forms.Form):
id = forms.IntegerField(widget=forms.HiddenInput)
Try to use form = CreateWizard.as_view([StepOneForm, StepTwoForm, StepThreeForm], initial=initial)