Model Form subclassing and hiding elements - django

Accordingly to the Django docs I can do the following:
class Article(models.Model):
headline = models.CharField(max_length=200, null=True, blank=True,
help_text="Use puns liberally")
content = models.TextField()
class ArticleForm(ModelForm):
headline = MyFormField(max_length=200, required=False,
help_text="Use puns liberally")
class Meta:
model = Article
In my case I would like "headline" not to be displayed at all as an option in a subclass. What is the best method to do that? I already tried "exclude"
class ArticleForm(ModelForm):
class Meta:
model = Article
exclude = ["headline"]
But since it is declared in the parent it is rendered anyways. Also I tried declaring it as headline = "" but same result.
Solution:
def __init__(self, *args, **kwargs):
super(NameOfSubclassedForm, self).__init__(*args, **kwargs)
del self.fields['headline'] # field that needs removing
TIA
*update: in my original post I placed exclude outside meta by accident
*update2: bug already reported: https://code.djangoproject.com/ticket/13971
*update3: Added Solution

I believe you simply have put your exclude in the wrong place. Try it like this:
class ArticleForm(ModelForm):
class Meta:
model = Article
exclude = ("headline",)
Check out the docs.

Related

Edit multiselect field?

So I have a ManyToMany field in my model and Django admin renders it as a multiselect field. It works fine and I have no issues — except that I can't Edit it after creating a record.
I tried Del key, mouse right-click, nothing worked. Looks like I have to delete the record and create it again?
This is the field I want to edit. I want to remove one or two of the above items. I'm on Windows.
Well it looks like there's a simpler solution:
(Courtesy of Webdev Hints)
Here's my models:
class Technology(models.Model):
title = models.CharField(max_length=10)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = 'Technologies'
class Project(models.Model):
title = models.CharField(max_length=100)
description = HTMLField()
technology = models.ManyToManyField(Technology, related_name='projects')
image = models.ImageField(upload_to='projects/')
def __str__(self):
return self.title
And the solution is to add the following to the admin.py:
#admin.register(Technology)
class TechnologyAdmin(admin.ModelAdmin):
pass
class TechnologyInline(admin.TabularInline):
model = Project.technology.through
#admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
inlines = (TechnologyInline,)
exclude = ('technology',)
Now the ManyToMany filed is editable.

Updating timestamps of related models

I have the following two models:
class Blog(TimeStampedModel):
summary = models.TextField()
status = models.CharField(max_length=255)
class Entry(TimeStampedModel):
author = models.CharField(max_length=255)
text = models.TextField()
blog = models.ForeignKey(Blog, models.CASCADE, related_name='entries')
Both models sublass a common meta-model that defines a timestamp for when each model was last updated:
class TimeStampedModel(models.Model):
last_changed = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
This works fine when saving each model individually. However, in my use case, when an Entry is updated, it should also reflect in the update of the last_changed timestamp of the associated Blog.
Is there any easy way to tell Django to also bump the timestamps of related models?
I admit that this is hacky, but you can override the save method of Entry model:
def save(self, *args, **kwargs):
self.blog.save()
super().save(*args, **kwargs)

In Django Rest Framework, how to limit number foreign key objects being serialized

I'm serialzing a Product model and its comments. Here's my simple code:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True)
class Meta:
model = Product
fields = [
'title',
'comment_set'
]
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Comment
fields = [
'text',
]
class Comment(models.Model):
product = models.ForeignKey(Product, null=True, blank=True, db_index=True)
class Product(models.Model):
title = models.CharField(max_length=50)
...
Problem:
If the product has many comments. For example, 500 comments. All 500 of them got serialized.
How to limit the result to a number of my own choosing, like 100 comments?
I've done some research before posting this but only found questions about filtering.
Thank you.
Define a new method on the Product model that returns a query set with a limited number of comments.
Then pass that method as the source of the CommentSerializer inside your ProductSerializer.
class Product(models.Model):
title = models.CharField(max_length=50)
def less_comments(self):
return Comment.objects.all().filter(product=self).order_by("-id")[:100]
Then in the ProductSerializer:
class ProductSerializer(serializers.HyperlinkedModelSerializer):
comment_set = CommentSerializer(many=True, read_only=True, source="less_comments")
PS: Wrote the codes from memory, didn't test them. But should work.
You can write custom ListSerializer and put in CommentSerializer, then create custom field in ProductSerializer, wich source based on default related name:
class LimitedListSerializer(serializers.ListSerializer):
def to_representation(self, data):
data = data.all()[:100]
return super(FilteredListSerializer, self).to_representation(data)
class CommentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
list_serializer_class = LimitedListSerializer
model = Comment
fields = [
'text',]
class Product(serializers.HyperlinkedModelSerializer):
related_comments = CommentSerializer(many=True, read_only=True, source='comment_set')
when you pass many=True, list serrializer will be called.
You'll want to work on the CommentSerializer's queryset to control which ones you keep.
You'll be able to do that by overriding get_queryset. For example, to filter them against the current user. Note that I took this example because it highlights how to use the request's context to filter against:
class CommentSerializer(serializers.HyperlinkedModelSerializer):
def get_queryset(self):
user = self.context['request'].user
queryset = Comment.objects.filter(user=user)
return queryset
class Meta:
model = Comment
fields = [
'text',
]

In Django 1.7 ModelMultipleChoiceField specify/filter all related subtasks must share a project

I'm trying to filter my Task form so that when you select any subtasks, they must share the 'super-tasks' project.
Here's a simple look on my task and dependency model:
class Task(models.Model):
title = models.CharField(max_length=100)
description = models.TextField(blank=True)
project = models.ForeignKey('Project', related_name="tasks")
dependency = models.ManyToManyField('self', through='Dependency', null=True,
blank=True, through_fields=('task', 'sub_task'), symmetrical=False)
class Dependency(models.Model):
task = models.ForeignKey(Task, related_name="dependency_task")
sub_task = models.ForeignKey(Task, related_name="dependency_sub_task")
And this is what I'm 'trying to get away with' on my form:
class TaskForm(forms.ModelForm):
subtasks=forms.ModelMultipleChoiceField(queryset=
Task.objects.filter(project=task.project))
class Meta:
model = Task
exclude = ('project',)
I mean it looks like the right 'sort' of thing, but PyLint says it doesn't recognise 'task'.
If I try to get the task by saving, PyLint also warns me it won't work:
task = forms.ModelForm.save(commit=False)
PyLint is expecting a 'self' argument in the save method, but one doesn't exist.
How do I accomplish this?
task variable is not available at the time of TaskForm definition. You should set up subtask's queryset in the constructor:
class TaskForm(forms.ModelForm):
subtasks=forms.ModelMultipleChoiceField(queryset=Task.objects.none())
class Meta:
model = Task
exclude = ('project',)
def __init__(self, *args, **kwargs):
super(TaskForm, self).__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['subtasks'].queryset = Task.objects.filter(
project=self.instance.project)
To pass current task to the form use instance argument:
form = TaskForm(instance=Task.objects.get(pk=1))

How to order the results of a ForeignKey relationship in a Django form?

I have this models in Django
class Country(models.Model):
name = models.CharField(max_length=80)
class Person(models.Model):
first_name = models.CharField(max_length=100, db_index=True)
last_name = models.CharField(max_length=100, db_index=True)
country = models.ForeignKey(Country)
and this ModelForm
class PersonForm(forms.ModelForm):
class Meta:
model = Person
when I use this form in a template, everything works fine, but the country list in the <select> appears disordered. How can I order it?
You can use the ordering property:
class Country(models.Model):
name = models.CharField(max_length=80)
class Meta:
ordering = ["name"]
If you set the ordering to the Country class, it shall display them as you want.
If you can't or don't want to use the ordering attribute in class Meta of model, you also can do this:
You need make a Form object, something like:
from django import forms
class PersonForm(forms.ModelForm):
country = forms.ModelChoiceField(queryset=Country.objects.all().order_by('name'))
class Meta:
model = Person
field types for formsmodels
There's 2 good answers here, but I wanted to retain help_text, blank, and other settings from the model without having to repeat them and also not change the default ordering on the model. Here's what I did:
class PersonForm(forms.ModelForm):
class Meta:
model = Person
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
self.fields['country'].queryset = self.fields['country'].queryset.order_by('name')
Essentially I just updated the queryset on the automatically added field to order the way I wanted.
try adding this into class Meta, inside class Person:
ordering = ['country']
http://docs.djangoproject.com/en/dev/ref/models/options/#ordering
In view.py
First: you create the form
form = YourForm(request.POST)
Later your set the query:
form.fields['country '].queryset = YourDBTable.objects.all().order_by('Your_Attr')