Django, Add value to ManyToManyField when save changes of the model - python

I use Django 1.7.11. I have models:
#I use django-categories app here
class Category(CategoryBase):
pass
class Advertisment(models.Model):
title = models.CharField(max_length=255, blank=True)
category = models.ForeignKey(Category, related_name='category')
all_categories = models.ManyToManyField(Category, blank=True, related_name='all_categories')
I need field "all_categories" contains "category" and all it's parent categories. I tried to use post_save, but it doesn't change any value. It even doesn't change title field. It doesn't work when I create model throught admin interface and works with custom form.
#receiver(post_save, sender=Advertisment, dispatch_uid="update_stock_count")
def update_stock(sender, instance, **kwargs):
categ = instance.category
instance.all_categories.add(categ)
for parent in categ.get_ancestors():
if parent not in instance.all_categories.all():
instance.all_categories.add(parent)
m2m_changed doesn't help too because ManyToManyField is empty and has no changes. How can I add a value from ForeignKey to ManyToMany field? What should I do in order to it works in admin interface.

I've found the solution. In admin class need to add a function save_model like this:
class AdvertismentAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if obj.category:
category_list=[]
category = obj.category
category_list.append(category)
for parent in category.get_ancestors():
if parent not in category_list:
category_list.append(parent)
form.cleaned_data['all_categories'] = category_list
super(AdvertismentAdmin, self).save_model(request, obj, form, change)

Related

Django form not populating with POST data

SOLUTION AT THE BOTTOM
Problem: Django form populating with list of objects rather than values
Summary: I have 2 models Entities and Breaks. Breaks has a FK relationship to the entity_id (not the PK) on the Entities model.
I want to generate an empty form for all the fields of Breaks. Generating a basic form populates all the empty fields, but for the FK it generates a dropdown list of all objects of the Entities table. This is not helpful so I have excluded this in the ModelForm below and tried to replace with a list of all the entity_ids of the Entities table. This form renders as expected.
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
exclude = ('entity',)
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all().values_list('entity_id', flat=True))
The below FormView is the cbv called by the URL. As the below stands if I populate the form, and for the FK column entity_id choose one of the values, the form will not submit. By that field on the form template the following message appears Select a valid choice. That choice is not one of the available choices.
class ContactFormView(FormView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
My initial thoughts were either that the datatype of this field (string/integer) was wrong or that Django needed the PK of the row in the Entities table (for whatever reason).
So I added a post function to the FormView and could see that the request.body was populating correctly. However I can't work out how to populate this into the ModelForm and save to the database, or overcome the issue mentioned above.
Addendum:
Models added below:
class Entity(models.Model):
pk_securities = models.AutoField(primary_key=True)
entity_id = models.CharField(unique=True)
entity_description = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class Breaks(models.Model):
pk_break = models.AutoField(primary_key=True)
date = models.DateField(blank=True, null=True)
entity = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
commentary = models.CharField(blank=True, null=True)
active = models.BooleanField()
def get_absolute_url(self):
return reverse(
"item-update", args=[str(self.pk_break)]
)
def __str__(self):
return f"{self.pk_break}"
class Meta:
managed = False
db_table = 'breaks'
SOLUTION
Firstly I got this working by adding the following to the Entity Model class. However I didn't like this as it would have consequences elsewhere.
def __str__(self):
return f"{self.entity_id}"
I found this SO thread on the topic. The accepted answer is fantastic and the comments to it are helpful.
The solution is to subclass ModelChoiceField and override the label_from_instance
class EntityChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.entity_id
I think your problem is two fold, first is not rendering the dropdown correctly and second is form is not saving. For first problem, you do not need to do any changes in ModelChoiceField queryset, instead, add to_field_name:
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all(), to_field_name='entity_id')
Secondly, if you want to save the form, instead of FormView, use CreateView:
class ContactFormView(CreateView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
model = Breaks
In Django, the request object passed as parameter to your view has an attribute called "method" where the type of the request is set, and all data passed via POST can be accessed via the request. POST dictionary. The view will display the result of the login form posted through the loggedin. html.

Django - formset working with multiple forms

I have three different models illustrated as below and I want to be able to edit three different forms in formset.
models.py
class Parent(models.Model):
parent_name = models.CharField(max_length=20)
class Child(models.Model):
child_name = models.CharField(max_length=20)
parent = models.ForeignKey("Parent",on_delete=models.PROTECT)
birth_place = models.OneToOneField("BirthPlace", on_delete=models.PROTECT)
class BirthPlace(models.Model):
place_name = models.CharField(max_length=20)
forms.py
ChildrenFormset = inlineformset_factory(Parent, Child, fields='__all__', extra=0)
So far, I have managed to create a formset where I can work with Parent and Child. Also, in html, I have written some javascript to add a child form dynamically.
Now the problem is embedding form for BirtPlace. I have tried the below:
def add_fields(self, form, index):
super(ChildrenFormset, self).add_fields(form, index)
form.initial['birth_place_name']=form.instance.birthplace.place_name
However, it throws RelatedObjectDoesNotExist.
Can you please help?
Thanks

How to create a timestamp on submit in Django class based views for Update?

I'm new to class based views in Django and was using a simple UpdateView CBV. The view has many fields that is being displayed on a template, except one; the Date field. How do I add a timestamp to this field? The timestamp should be added only the first time the object is created. From the next time onwards, it should only show the timestamp that was added the first time. This is my code:
class ReviewsEdit(UpdateView):
model = ProductReview
fields = ['title', 'body', 'score', 'responder_name', 'response']
template_name = 'review_system/review_edit.html'
context_object_name = 'review'
The ProductReview model has all the above fields with another Date field, I'd like to save it with timestamp data when the submit button was clicked - only if it is blank when submitting. Is this possible with CBV?
If you want to implement fields that let you know when the object was created and/or last edited you can achieve that with these Django fields:
class TouchStampModel(Model):
created = DateTimeField(auto_now_add=True, editable=False, null=False, blank=False)
last_modified = DateTimeField(auto_now=True, editable=False, null=False, blank=False)
class Meta:
abstract = True
Any model subclasses will have those fields and they won't be editable by default (won't show up in forms for editing), and last_modified will be updated automatically on each DB update while created will be set to the DB insert time.
Of course, you can modify your existing model's DateTimeField. There is no need for the superclass unless you have several models that require created and last_modified.
class ReviewModel(Model):
created_at = DateTimeField(auto_now_add=True, editable=False, null=False, blank=False)
why don't you just display this on the template then.
obj.created_at
this will always show the time the review was created
I got it, I had to over-ride the save() method to get it done. It works something like this. After defining your models, over-ride save() method.
def save(self, *args, **kwargs):
if not self.your.field:
self.date_field = datetime.datetime.now()
super(ModelName, self).save(*args, **kwargs)

Django - Restricting ModelChoiceField to valid values

Models.py:
class Comment(models.Model):
user = models.ForeignKey(User)
document = models.ForeignKey(Document)
section = models.ForeignKey(Section, null=True, blank=True)
description = models.TextField(null=True, blank=True)
Forms.py:
class CommentForm(ModelForm):
class Meta:
model = Comment
fields = ('section', 'description')
Each Comment belongs to a Section of a Document. Each Document hasmany Sections. However, the ModelChoiceField printed out by Django will contain Sections for ALL Documents.
How do I tell Django to only print the Sections that belong to a particular Document?
I looked at ModelFormSets - Changing the queryset but I don't think it's quite what I'm after.
If all you need to do is adjust the admin site you can override the formfield_for_foreignkey method on your django admin class.
From the docs:
ModelAdmin.formfield_for_foreignkey(self,db_field, request, **kwargs)
The formfield_for_foreignkey method on a ModelAdmin allows you to override the
default formfield for a foreign key field. For example, to return a subset
of objects for this foreign key field based on the user:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This uses the HttpRequest instance to filter the Car
foreign key field to only display the cars owned by the User instance.
I think you want to change the ModelChoiceField's queryset and not the queryset of the formset.

Can you change a field label in the Django Admin application?

As the title suggests. I want to be able to change the label of a single field in the admin application. I'm aware of the Form.field attribute, but how do I get my Model or ModelAdmin to pass along that information?
the verbose name of the field is the (optional) first parameter at field construction.
If your field is a property (a method) then you should use short_description:
class Person(models.Model):
...
def address_report(self, instance):
...
# short_description functions like a model field's verbose_name
address_report.short_description = "Address"
As Javier suggested you can use verbose name in your fields in model.py. Example as below,
class Employee(models.Model):
name = models.CharField(max_length = 100)
dob = models.DateField('Date Of Birth')
doj = models.DateField(verbose_name='Date Of Joining')
mobile=models.IntegerField(max_length = 12)
email = models.EmailField(max_length=50)
bill = models.BooleanField(db_index=True,default=False)
proj = models.ForeignKey(Project, verbose_name='Project')
Here the dob,doj and proj files will display its name in admin form as per the verbose_name mentioned to those fields.
from django.db import models
class MyClassName(models.Model):
field_name = models.IntegerField(verbose_name='Field Caption')
Building on Javier's answer; if you need one label in forms (on the front-end) and another label on admin it is best to set internal (admin) one in the model and overwrite it on forms. Admin will of course use the label in the model field automatically.
Use "verbose_name" to change a field name as the example below.
"models.py":
from django.db import models
class MyModel(models.Model): # Here
name = models.CharField(max_length=255, verbose_name="My Name")
If you want change the field label only on particular admin model without changing field of the model:
class MyModelAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
form.base_fields["name"].label = "New label"
return form

Categories

Resources