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()
Related
I have a Mezzanine Project and am trying to update the keywords on a blog entry. I am having difficulty getting the format correct to call KeywordsField.save_form_data this invokes a js that will update the keywords on a blog post. See below:
From Messanine/generic/fields.py
class KeywordsField(BaseGenericRelation):
"""
Stores the keywords as a single string into the
``KEYWORDS_FIELD_NAME_string`` field for convenient access when
searching.
"""
default_related_model = "generic.AssignedKeyword"
fields = {"%s_string": CharField(editable=False, blank=True,
max_length=500)}
def __init__(self, *args, **kwargs):
"""
Mark the field as editable so that it can be specified in
admin class fieldsets and pass validation, and also so that
it shows up in the admin form.
"""
super(KeywordsField, self).__init__(*args, **kwargs)
self.editable = True
def formfield(self, **kwargs):
"""
Provide the custom form widget for the admin, since there
isn't a form field mapped to ``GenericRelation`` model fields.
"""
from mezzanine.generic.forms import KeywordsWidget
kwargs["widget"] = KeywordsWidget
return super(KeywordsField, self).formfield(**kwargs)
def save_form_data(self, instance, data):
"""
The ``KeywordsWidget`` field will return data as a string of
comma separated IDs for the ``Keyword`` model - convert these
into actual ``AssignedKeyword`` instances. Also delete
``Keyword`` instances if their last related ``AssignedKeyword``
instance is being removed.
"""
from mezzanine.generic.models import Keyword
related_manager = getattr(instance, self.name)
# Get a list of Keyword IDs being removed.
old_ids = [str(a.keyword_id) for a in related_manager.all()]
new_ids = data.split(",")
removed_ids = set(old_ids) - set(new_ids)
# Remove current AssignedKeyword instances.
related_manager.all().delete()
# Convert the data into AssignedKeyword instances.
if data:
data = [related_manager.create(keyword_id=i) for i in new_ids]
# Remove keywords that are no longer assigned to anything.
Keyword.objects.delete_unused(removed_ids)
super(KeywordsField, self).save_form_data(instance, data)
From my Views.py
class PubForm(forms.ModelForm):
class Meta:
model = BlogPost
fields = ['keywords']
def UpdatePub(request, slug):
blog_post = BlogPost.objects.get(id=slug)
if request.method == 'POST':
form = PubForm(request.POST)
if form.is_valid():
publish_date = datetime.datetime.now()
blog_post.status = CONTENT_STATUS_PUBLISHED
publish_date=publish_date
tags=form.cleaned_data['keywords']
blog_post.save()
KeywordsField.save_form_data(user,blog_post,tags)
return HttpResponseRedirect('/write/')
else:
form = PubForm(instance=blog_post)
return render(request, 'blog_my_pub.html', {'form' : form})
It complains that the field 'user' has no attribute 'name'. I have tried many different values for this parameter and cannot figure it out. Any help would be appreciated.
Thanks for any input.
GOAL: Send a dictionary of data to a form, to be used in a dropdown boxself.
Views.py
form = FormsdbForm(initial={'user': default_state})
# (to set the default value of the 'user' field)
Forms.py
class FormsdbForm(forms.ModelForm):
ROOMLIST = (('roomID_abcdefghi','Room ABC'),
('roomID_jklmnopqr','Room JKL'))
roomid = forms.ChoiceField(required=False, choices=ROOMLIST)
class Meta:
model = Formsdb
fields = ('user', 'uniqueid', 'roomid')
The above setup displays a form where the field 'roomid' is a dropdown box showing to entries:
Room ABC
Room JKL
After saving, the database is populated with the matching 'RoomID_xxxxxxxxx'
Perfect so far!
In my Views.py I have a dictionary (that I can easily convert into a list-of-lists) with the data that is now statically configured in Forms.py (ROOMLIST).
QUESTION: How can I pass this dictionary (or list) to the form so it displays a dropdown box with choices?
This would replace the current "ROOMLIST" variable and it could easily contain 400-600 entries.
The view:
from django.views.generic import FormView
class FormsdbView(FormView):
# ...
def get_form_kwargs(self):
kwargs = super(FormsdbView, self).get_form_kwargs()
ROOMLIST = (('roomID_abcdefghi','Room ABC'),
('roomID_jklmnopqr','Room JKL'))
kwargs['roomlist'] = ROOMLIST
return kwargs
If you're not using FormView, you might also do form = FormsdbForm(initial={'user': default_state}, roomlist=ROOMLIST)
The form:
from django import forms
class FormsdbForm(forms.ModelForm):
roomid = forms.ChoiceField(required=False)
# ...
def __init__(self, *args, **kwargs):
self.roomlist = kwargs.pop('roomlist', None)
super(FormsdbForm, self).__init__(*args, **kwargs)
self.fields['roomid'].choices = self.roomlist
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
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 + ...
I'm trying to use get_readonly_fields in a TabularInline class in Django:
class ItemInline(admin.TabularInline):
model = Item
extra = 5
def get_readonly_fields(self, request, obj=None):
if obj:
return ['name']
return self.readonly_fields
This code was taken from another StackOverflow question:
Django admin site: prevent fields from being edited?
However, when it's put in a TabularInline class, the new object forms don't render properly. The goal is to make certain fields read only while still allowing data to be entered in new objects. Any ideas for a workaround or different strategy?
Careful - "obj" is not the inline object, it's the parent. That's arguably a bug - see for example this Django ticket
As a workaround to this issue I have associated a form and a Widget to my Inline:
admin.py:
...
class MasterCouponFileInline(admin.TabularInline):
model = MasterCouponFile
form = MasterCouponFileForm
extra = 0
in Django 2.0:
forms.py
from django import forms
from . import models
from feedback.widgets import DisablePopulatedText
class FeedbackCommentForm(forms.ModelForm):
class Meta:
model = models.MasterCouponFile
fields = ('Comment', ....)
widgets = {
'Comment': DisablePopulatedText,
}
in widgets.py
from django import forms
class DisablePopulatedText(forms.TextInput):
def render(self, name, value, attrs=None, renderer=None):
"""Render the widget as an HTML string."""
if value is not None:
# Just return the value, as normal read_only fields do
# Add Hidden Input otherwise the old fields are still required
HiddenInput = forms.HiddenInput()
return format_html("{}\n"+HiddenInput.render(name, value), self.format_value(value))
else:
return super().render(name, value, attrs, renderer)
older Django Versions:
forms.py
....
class MasterCouponFileForm(forms.ModelForm):
class Meta:
model = MasterCouponFile
def __init__(self, *args, **kwargs):
super(MasterCouponFileForm, self).__init__(*args, **kwargs)
self.fields['range'].widget = DisablePopulatedText(self.instance)
self.fields['quantity'].widget = DisablePopulatedText(self.instance)
in widgets.py
...
from django import forms
from django.forms.util import flatatt
from django.utils.encoding import force_text
class DisablePopulatedText(forms.TextInput):
def __init__(self, obj, attrs=None):
self.object = obj
super(DisablePopulatedText, self).__init__(attrs)
def render(self, name, value, attrs=None):
if value is None:
value = ''
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(self._format_value(value))
if "__prefix__" not in name and not value:
return format_html('<input{0} disabled />', flatatt(final_attrs))
else:
return format_html('<input{0} />', flatatt(final_attrs))
This is still currently not easily doable due to the fact that obj is the parent model instance not the instance displayed by the inline.
What I did in order to solve this, was to make all the fields, in the inline form, read only and provide a Add/Edit link to a ChangeForm for the inlined model.
Like this
class ChangeFormLinkMixin(object):
def change_form_link(self, instance):
url = reverse('admin:%s_%s_change' % (instance._meta.app_label,
instance._meta.module_name), args=(instance.id,))
# Id == None implies and empty inline object
url = url.replace('None', 'add')
command = _('Add') if url.find('add') > -1 else _('Edit')
return format_html(u'%s' % command, url)
And then in the inline I will have something like this
class ItemInline(ChangeFormLinkMixin, admin.StackedInline):
model = Item
extra = 5
readonly_fields = ['field1',...,'fieldN','change_form_link']
Then in the ChangeForm I'll be able to control the changes the way I want to (I have several states, each of them with a set of editable fields associated).
As others have added, this is a design flaw in django as seen in this Django ticket (thanks Danny W). get_readonly_fields returns the parent object, which is not what we want here.
Since we can't make it readonly, here is my solution to validate it can't be set by the form, using a formset and a clean method:
class ItemInline(admin.TabularInline):
model = Item
formset = ItemInlineFormset
class ItemInlineFormset(forms.models.BaseInlineFormSet):
def clean(self):
super(ItemInlineFormset, self).clean()
for form in self.forms:
if form.instance.some_condition:
form.add_error('some_condition', 'Nope')
You are on the right track. Update self.readonly_fields with a tuple of what fields you want to set as readonly.
class ItemInline(admin.TabularInline):
model = Item
extra = 5
def get_readonly_fields(self, request, obj=None):
# add a tuple of readonly fields
self.readonly_fields += ('field_a', 'field_b')
return self.readonly_fields