How to increase the counter on my Foreign Key Model - django

I have the following doubt:
Let's assume that my Django project has the following models:
class State(models.Model):
initials = models.CharField('Initials', max_length=2, blank = False)
name = models.CharField('State', max_length=50, blank = False)
count = models.IntegerField('Foo Counter', default=0)
....
class Foo(models.Model):
name = models.CharField('Name', max_length=50, blank = False)
state = models.ForeignKey(State, verbose_name='State', related_name='state_fk', on_delete=models.DO_NOTHING),
....
So i have a form to add Foo instances to my db:
class FooForm(forms.ModelForm):
class Meta:
model = Foo
fields = '__all__'
this is the view.py file:
def home(request):
template_name = 'home.html'
form = FooForm(request.POST or None)
if form.is_valid():
salvar = form.save(commit=False)
salvar.save()
return redirect('FooApp:home')
else:
context = {
'form': form
}
return render(request, template_name, context)
I need that, every time the user registers a new 'Foo' the counter of the 'State' chosen by him is increased by 1, i search a lot here and in docs of Django but i could not find a way to do this.

Why do you need count defined as a model field if it's dependent on a database computation, and not something that will be entered from outside?
As mentioned before, you can add logic in the application to update count value from State to self.foo_set.count()
However, I think that it might worth looking into a different approach which would be defining a cached_property on State as it follows:
#cached_property
def count(self):
return self.foo_set.count()
In this way, you'll be able to access State.count wherever you want in the application and get the right value without worrying to keep it updated.

You may not need to keep track of count manually like that. For any instance of State you can always call:
state.foo_set.count()
That will always give you the current count.

Related

limit one record to be default in django model

What is the best way to limit only one record to be default in django
I have a model where i have a flag for default
class BOMVersion(models.Model):
name = models.CharField(max_length=200,null=True, blank=True)
material = models.ForeignKey(Material)
is_default = models.BooleanField(default=False)
I want to have only one value to be default for the same material but this material can have a lot of non default ones.
It was odd that this question was not addressed more often. If I have a default record, I want to record that in the class as a member variable. And to determine if an instance is the default, I want to compare the class default member variable with the instance id. Unfortunately I could not figure out how to access class variables and instance variables nicely in the same class function in Python (may be someone can comment), but this does not require to hit the database for a default or store a bunch of records pointing to a default. Just one member variable in the class.
After I wrote this, I realized every time the application is restarted, default is reset to None, so you will have to store this in a database. I have updated my answer accordingly. However, checking that the member variable is not null, and only hitting the database if it is would reduce hits here. The model I used was:
class RecordOfInterest(models.Model):
"""
Record Records of interest here. Stores ID, and identifying character
"""
# asume maximum 64 character limit for the model.
model_name = models.CharField(max_length=64, unique=True)
record_no = models.IntegerField()
human_ident = models.CharField(max_length=64, help_text='How is this of interest')
# Id it as default, deposit, ... Don't bother indexing, as there will only be a few
def __unicode__(self):
return u'Model %s record %d for %s' % (self.model_name, self.record_no, self.human_ident)
class Meta:
unique_together = ('model_name', 'human_ident')
class Product(models.Model):
"""
Allow one product record to be the default using "Product.default = prod_instance"
check default with "Product.is_default(prod_instance)"
"""
default = None # set this to id of the default record
cart_heading = models.CharField(max_length=64, blank=True)
country = CountryField()
pricing = models.ForeignKey(
'Price', blank=True, null=True, related_name='visas', on_delete=models.SET_NULL)
#classmethod
def is_default(cls, obj):
if cls.default_no == None:
try:
cls.default_no = RecordOfInterest.objects.get(model_name=cls.__name__, human_ident='default')
except RecordOfInterest.DoesNotExist:
default_no = None
return cls.default_no == obj.id
#classmethod
def set_default(cls, obj):
try:
default_rec = RecordOfInterest.objects.get(model_name=cls.__name__, human_ident='default')
except RecordOfInterest.DoesNotExist:
RecordOfInterest.objects.create(model_name=cls.__name__, record_no=obj.id, human_ident='default')
else:
if default_rec.record_no != obj.id:
default_rec.record_no = obj.id
default_rec.save()
cls.default_no = obj.id
return
Saving the ID in settings.py if it is static.
Save it into a separate "default" table with one record (or use the most recent) if it's dynamic.
Save the default in another table like this:
class BOMVersion(models.Model):
name = models.CharField(max_length=200,null=True, blank=True)
material = models.ForeignKey(Material)
class BOMVersionDefault(model.Models)
time_set= models.Datetime(auto_created=True)
default_material = models.ForiegnKey(Material)
To query:
default = BOMVerDefault.objects.latest(time_set).get().default_material
If you have several material types that each need a default then default_material would be a field in a material-type table.
Getting one record to be default in a table is most basic requirement we developer come face to face, after spending couple of hours over it, i think a neat and clean solution in django would be update all records to default false if current form instance has default value to be "true" and then save the record.
class FeeLevelRate(TimeStampedModel):
"""
Stores the all the fee rates depend on feelevel
"""
feelevel = models.ForeignKey(FeeLevel, on_delete= models.PROTECT)
firstconsultfee = models.DecimalField(_('First Consultation Charges'),max_digits=10,decimal_places=2,blank=True)
medcharges = models.DecimalField(_('Medicines Charges per Day'),max_digits=10,decimal_places=2,blank=True)
startdate = models.DateField(_("Start Date "), default=datetime.date.today)
default_level = models.BooleanField(_('Is Default Level?'),default=False)
class Meta:
constraints = [
models.UniqueConstraint(fields=["feelevel","startdate"], name='unique_level_per_date'),
]
def __str__(self):
return "%s applicable from (%s)" % ( self.feelevel, self.startdate.strftime("%d/%m/%Y"))
class FeeLevelRateCreate(CreateView):
model = FeeLevelRate
fields = ['feelevel', 'firstconsultfee', 'medcharges', 'startdate', 'default_level']
context_object_name = 'categories'
success_url = reverse_lazy('patadd:feerate_list')
def form_valid(self, form):
# Update all the default_level with false.
#UserAddress.objects.filter(sendcard=True).update(sendcard=False)
if form.instance.default_level:
FeeLevelRate.objects.filter(default_level=True).update(default_level=False)
return super().form_valid(form)

Django forms with prefixes not updating on save()

I iterate through a list of Block objects, instantiate a ModelForm for each of them with a mapping dictionary that links a block_type to a ModelForm model, and then append the form to a list which I pass off to a template for display.
for block in blocks:
block_instance = block_map[block.block_type].objects.get(id=block.id)
new_form = block_forms[block.block_type]
new_form_instance = new_form(
request.user,
request.POST or None,
instance=block_instance,
prefix = block.id
)
form_zones.append(new_form_instance)
Later, while checking request.POST I validate each form
if request.POST.get("save_submit"):
for zone_form_check in story_zones:
for block_form_check in zone_form_check:
if block_form_check.is_valid():
print(block_form_check.cleaned_data.get("content"))
saved = block_form_check.save()
print(saved.content)
valid = True
if valid:
return redirect("Editorial:content", content_id=content_id)
cleaned_data.get("content") produces the updated data, but even after calling save() on the valid form, saved.content produces the object's old content attribute. In other words, a valid form is having save() called upon it, but it is not saving.
One of the forms in question (and currently my only one) is:
class Edit_Text_Block_Form(ModelForm):
content = forms.CharField(widget = forms.Textarea(
attrs = {
"class": "full_tinymce"
}),
label = "",
)
class Meta:
model = TextBlock
fields = []
def __init__(self, user, *args, **kwargs):
self.user = user
super(Edit_Text_Block_Form, self).__init__(*args, **kwargs)
The model in question is a TextBlock, which inherits from a Block objets. Both of those are below:
class Block(models.Model):
zone = models.ForeignKey(Zone)
order = models.IntegerField()
weight = models.IntegerField()
block_type = models.CharField(max_length=32, blank=True)
class Meta:
ordering = ['order']
def delete(self, *args, **kwargs):
# Calling custom delete methods of child blocks
child = block_map[self.block_type].objects.get(id=self.id)
if getattr(child, "custom_delete", None):
child.custom_delete()
# Overriding delete to check if there are any other blocks in the zone.
# If not, the zone itself is deleted
zones = Block.objects.filter(zone=self.zone).count()
if zones <= 1:
self.zone.delete()
# Children of Block Object
class TextBlock(Block):
content = models.TextField(blank=True)
Any ideas for why calling saved = block_form_check.save() isn't updating my model?
Thanks!
I think this is because you've effectively excluded all the model fields from the form by setting fields = [] in the form's Meta class. This means that Django no longer relates the manually-defined content field on the form with the one in the model.
Instead, set fields to ['content'], and it should work as expected.
TL;DR form name cannot start with a number as per html4 specs
Try prefix = "block_%s" % block.id

Django Select Drop Down menu Flow

What I'm trying to do is a 2 tier search with drop down menus using Select widget, the results will be a listing of the fields from my Meta.model. the first Tier is a a State listing from State.model. Upon a select it is supposed to list out all of the cities with in the selected state, the problem I'm having (and I think its due to my lack of knowledge) is that the city listing is not filtered but a listing of all cities in my database regardless of state. I'm not sure where or how to pass my variable to be able invoke my .filter() statement.
models.py
class Meta(models.Model):
rcabbr = models.CharField(max_length = 15)
slug = models.SlugField(unique=False)
state = models.ForeignKey('State')
rc_state = models.CharField(max_length = 3)
oerp = models.CharField(max_length=18)
subgrp = models.SlugField()
sonus_pic = models.CharField(max_length=8)
ems = models.CharField(max_length=14)
agc = models.CharField(max_length=14)
def __unicode__(self):
return self.rcabbr
class State(models.Model):
name = models.CharField(max_length=2)
slug = models.SlugField(unique=True)
state_long = models.CharField(max_length=15)
owning_site = models.CharField(max_length=12)
def __unicode__(self):
return self.name
return self.state_long
forms.py
class states(forms.Form):
invent = [(k.name,k.state_long) for k in State.objects.all()]
rclist = forms.ChoiceField(widget=forms.Select, choices=invent)
class rateCenter(forms.Form):
invention = [(k.id,k.rcabbr,k.rc_state) for k in Meta.objects.all()]
rcviews = forms.ChoiceField(widget=forms.Select, choices=invention)
views.py
def StateAll(request):
""" This lists out all of the states within the database that
are served"""
rclist = states()
return render(request, 'statelist.html',{'rclist': rclist})
def RcView(request):
""" this should list all the rateCenters within
the state that was selected in StateAll() """
rclist = request.GET['rclist']
forms = rateCenter()
return render(request, 'rclist.html',{'forms': forms})
Logic tells me I should to do my .filter() statement in the forms.py but unsure how to pass the result from the request.GET in StateAll() view. I do have the debug_toolbar installed so I can see the variable u'rclist' and the value u'LA' (my test state). I had this working 100% using hyperlinks however the size of my test database is miniscule in comparison to what is going to be in the production version and HREF's are just not possible.
my understanding is:
ChainedForeignKey(LinkedModel, LinkedModel.field = "field in first Tier", chained_model_field = "current model_field")
so simple model should I think be something like this?
def State(model.models):
name = models.CharField(max_length=20) #this is the state abbreviation
state_long = models.CharFeild(max_length=20)#this is state long form
def Meta(model.models):
state = models.CharField(max_length=20)
slug = models.SlugField(unique = False) #same values as rcabbr
rcabbr = ChainedForeignKey(State, chained_field = "state_long",
chained_model_field = "slug")
.....
Does that look about right........so the First Field in the drop down should be the State_long, once selected the next should be the slug?. at which time the slug should be passed to my urls and the views for the that final page.
I am going to try this however I'm not 100% sure how to do my views and if I need to do something with forms page or does this cover it? The documentation is not user friendly for someone new to this so any input would be most appreciated!
There are many third party libraries django-smart-selects and dajax come to mind - that will automate this for you along with provide you the necessary javascript to filter the form fields on the fly.
If you are investigating those, here is how you would do it with just the django forms:
class RateCenterForm(forms.Form):
rate_center = forms.ModelChoiceField(queryset=Meta.objects.all())
def __init__(self, *args, **kwargs):
state = kwargs.pop('state')
super(RaterCenterForm, self).__init__(*args, **kwargs)
self.fields['rate_center'].queryset = Meta.objects.filter(state=state)
A ModelChoiceField is a select drop down that takes its values from a model.
Now in your view, you would call it like this:
def show_rate_centers(request):
form = RateCenterForm(state='SomeState')
# .. your normal logic here

Django custom update model form - display selected field of related model rather than foreign key id.

My question is: is there a way to create custom model form that will use a specified field from a related model rather than the related model's id?
To clarify, if I have the following two models:
class ModelOne(models.Model):
id = models.AutoField(primary_key = True)
name = models.CharField(unique = True, blank = False, null = False)
class ModelTwo(models.Model):
id = models.AutoField(primary_key = True)
parent = models.ForeignKey(ModelOne, blank = False, null = False)
attribute_1 = models.CharField(blank = False, null = False)
attribute_2 = models.IntegerField(blank = False, null = False)
Now if I create an UpdateView on ModelTwo using a ModelForm then the parent field will be pre-filled with the corresponding id from ModelOne. However I want it to display the name attribute of ModelOne and then on form submission parse the unique name (of ModelOne) to the corresponding instance of ModelOne. The reason I want to do it this way, is that I believe it is far more intuitive from a users perspective to deal with the name of ModelOne (when updating a ModelTwo instance) rather than its "id".
Any suggestions of how I can do this?
Firstly, try defining the unicode method on ModelOne. It might not apply to the solution, but it's worth having - it will drive the text values in a form Select widget...
def __unicode__(self):
'''Warning: be careful not to use related objects here,
could cause unwanted DB hits when debugging/logging
'''
return self.name
If that's not sufficient, something like this might work (it is adapted from a form I have that updates the user's name attached to a profile)...
class M2Form(forms.ModelForm):
m1_name = forms.CharField()
class Meta:
model = ModelTwo
def save(self, *args, **kw):
# Update your name field here, something like
if self.cleaned_data.get('m1_name'):
self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('m1_name'))
return super(M2Form, self).save(*args, **kw)
This is untested, and you'll likely need to adapt this to validate that the name exists and make sure the original parent field doesn't appear on the form. With any luck, the first answer covers what I think your question is.
Using Rog's answer as a starting point and delving through some of Django's internals I eventually came to a working solution. Given my level of Django knowledge, I imagine there is a better way of doing this; so if you have another method please add it.
So based on the above two models, I created the following form class:
class CustomForm(forms.ModelForm):
parent = models.CharField(label='Name')
class Meta:
model = ModelTwo
exclude = ['parent']
def __init__(self,*args,**kwargs):
# The line of code below is the one that I was looking for. It pre-populates
# the "parent" field of the form with the "name" attribute of the related
# ModelOne instance.
kwargs['initial']['parent'] = kwargs['instance'].parent.name
super(CustomForm,self).__init__(*args,**kwargs)
# The next line is for convenience and orders the form fields in our desired
# order. I got this tip from:
# http://stackoverflow.com/questions/913589/django-forms-inheritance-and-order-of-form-fields
self.fields.keyOrder = ['parent','attribute_1','attribute_2']
def save(self, *args, **kwargs):
if self.cleaned_data.get('parent'):
# This section of code is important because we need to convert back from the
# unique 'name' attribute of ModelOne to the corresponding instance so that
# ModelTwo can be saved. Thanks goes to Rog for this section of code.
self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('parent'))
return super(CustomForm, self).save(*args, **kwargs)

Django - What's a good way to handle manytomany with intermediary table in forms and views

Suppose I have the following models -
class Item(models.Model):
name = models.CharField(max_length=150)
value = models.DecimalField(max_digits=12,decimal_places=2)
class Organization(models.Model):
name = models.CharField(max_length=150)
items = models.ManyToManyField(Item, through='Customizable')
class Customizable(models.Model):
organization = models.ForeignKey(Organization)
item = models.ForeignKey (Item)
value = models.DecimalField(max_digits=12,decimal_places=2)
More often than not, when items are "assigned" to an organization, they will have the same value as originally recorded in the related Item object. But in certain cases, an item assigned to an organization may have an overridden value (hence the intermediary model). Since overriding the original value happens rarely (but it does happen) I want to allow the user to simply select desired items from a list of Item instances to assign them to an organization instance. The user will then have the option of overriding individual values later after bulk assignment is complete.
So I have the following simple ModelForm -
class AssignItemsForm(forms.ModelForm):
items = forms.ModelMultipleChoiceField(queryset=Item.objects.all(),required=False,widget=forms.CheckboxSelectMultiple)
class Meta:
model = Organization
exclude = ('name',)
Now since I have a through model, a simple form.save() won't work. I need to
(i) save Customizable instances corresponding to the items selected by the user and
(ii) make sure the persisted Customizable instances have the proper value taken from the corresponding value taken from the item instance related by foreignkey .
I am trying to handle it in a view (but my mind is blocked) -
def assign_items(request, oid):
organization = Organization.objects.get(id=oid)
if request.method == 'POST':
form = AssignItemsForm(data=request.POST, instance=organization)
if form.is_valid():
current_organization = form.save(commit=False)
#
#placeholder to save Customizable instances here
#
return HttpResponseRedirect(reverse('redirect-someplace-else'))
else:
form = AssignItemsForm(instance=organization,)
return render_to_response("assign_items.html", {"form": form,}, context_instance=RequestContext(request))
You would have to use save_m2m method:
def assign_items(request, oid):
organization = Organization.objects.get(id=oid)
if request.method == 'POST':
form = AssignItemsForm(data=request.POST, instance=organization)
if form.is_valid():
current_organization = form.save(commit=False)
current_organization.save()
form.save_m2m()
return HttpResponseRedirect(reverse('redirect-someplace-else'))
else:
form = AssignItemsForm(instance=organization,)
return render_to_response("assign_items.html", {"form": form,}, context_instance=RequestContext(request))
Look here for more info:
http://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
I'd approach this in a different way. You have an intermediary model for your m2m. Hence I'd argue that AssignItemsForm should be backed by this intermediary model. Therefore I'd change it as follows:
# forms.py
class AssignItemsForm(forms.ModelForm):
value = forms.DecimalField(max_digits=12, decimal_places=2, required = False)
class Meta:
model = Customizable
Next, the matter of allowing users to choose a different value. In order to do this I've made the value field of the model optional (required = False). I then check if the user has supplied an explicit value. If not I assume that the Item's default value is to be used. For this I am overriding the clean method of the form:
def clean(self):
super(AssignItemsForm, self).clean()
value, item = self.cleaned_data.get('value'), self.cleaned_data.get('item')
if not value:
value = item.value
self.cleaned_data['value'] = value
return self.cleaned_data
And finally I tested this in admin.
# admin.py
from app.forms import AssignItemsForm
class CAdmin(admin.ModelAdmin):
form = AssignItemsForm
admin.site.register(Item)
admin.site.register(Organization)
admin.site.register(Customizable, CAdmin)
This way you can continue to use form.save() thereby avoiding custom manipulation in the view. You'll have to change your view a bit to make sure that the organization is auto selected for assigning items.
# views.py
def assign_items(request, oid):
organization = Organization.objects.get(id=oid)
if request.method == 'POST':
form = AssignItemsForm(data=request.POST.copy())
form.save()
else:
form = AssignItemsForm(initial = {'organization': organization})
...
Override the save method of the ModelForm. This way you won't have to repeat yourself if you need to use the form in multiple places.
See this answer for more details:
https://stackoverflow.com/a/40822731/2863603