I've got an admin form with a couple of inlines for displaying m2m fields, like so:
class ArticleAdmin(admin.ModelAdmin):
form = ArticleCustomAdminForm
inlines = (SpecificGemInline, SuiteInline,)
Base class looks something like that:
class Article(models.Model):
article_code = models.CharField(max_length=15)
gems = models.ManyToManyField(Gem, through='SpecificGem')
Model has a special field article_code that should aggregate some data from m2m fields represented in both inlines, so I've written a function create_code(instance) that does so by accessing model instance fields directly, something like that:
def create_code(instance):
article_code_part1 = SpecificGem.objects.filter(article=instance)
article_code_part2 = instance.suite_set.all()
instance.article_code = #do something with both parts
The problem is, when I call this function from overriden ModelAdmin's save_model() or model's save() functions, following instance m2m fields produces outdated results. Even retarded example below wouldn't help:
class ArticleAdmin(admin.ModelAdmin):
#...
def save_model(self, request, obj, form, change):
obj.save()
create_code(obj)
obj.save()
When I get into InlineFormset's clean() method, I have access to its forms' data so I could figure out a part of article_code even without actual saving... but I have two inlines.
So how do I find the topmost save method, so I could call my aggregation function after all models are validated and saved to db?
In order to catch changes to a ManyToManyField, you need to hook up the m2m_changed signal. You might want to have a look at the documentation for signals in general and the m2m_changed signal in particular.
Related
Apologize if this question has already been addressed before. Since I was not able to find a proper solution for this, had to ask.
I need to perform an action(an API call telling my clients to update models from their end) when there is a change in a model's Inline Admin fields from within the save_model() of the parent admin.
models.py
class Student(models.Model)
name = CharField()
age = DateField()
class Marks(models.Model)
student = ForeignKey(Student)
subject = CharField()
marks = IntegerField()
admin.py
class MarksInline(admin.TabularInline):
model = Marks
form = MarksForm
formset = MarksInlineFormSet
class StudentAdmin(admin.ModelAdmin):
form = StudentForm
inlines = [MarksInline, ]
I am able to achieve this by checking the form.changed_data from within the StudentAdmin save_model() and for the MarksInline models MarksInlineFormSet clean() method. The issue is my action will be called separately from each of these methods resulting in two calls, even though all I need is a single call to update the Student and Marks model in the clients end.
My problem would be solved if the save_model() of StudentAdmin could return the fields that has been changed via form.changed_data in MarksInline as well.
Tried to use post_save signals as well by implementing Field Tracker. But this too sent out separate post_save signal calls to the receiver function.
Does anyone know a work around using which I can figure out the changed fields of the InlineAdmin fields from within the parent Admins save_model() method.
I've two models:
First one:
class A(models.Model):
a_user = models.ForeignKey(User, unique=False, on_delete=models.CASCADE)
a_title = models.CharField("A title", max_length=500)
Second one:
class B(models.Model):
b_a = models.ForeignKey(A, verbose_name=('A'), unique=False, on_delete=models.CASCADE)
b_details = models.TextField()
Now, I'm using CreateView to create form for Value filling :
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
Then using this to render these field in templates.
Now, my problem is, while giving the field b_a ( which is the dropdown ), it list downs all the values of model A, but the need is to list only the values of model A which belongs to the particular logged in user, in the dropdown.
I've seen all the answers, but still not able to solve the problem.
The things I've tried:
limit_choices_to in models : Not able to pass the value of A in the limit_choices
form_valid : Don't have the model A in the CreateView, as only B is reffered model in B_Create
passing primary key of A in templates via url : Then there is no instance of A in the template so can't access. Also, don't want to handle it in templates.
I'm new to Django and still learning, so don't know to override admin form.
Please suggest the implemented way, if possible to the problem. I've researched and tried most of the similar questions with no result for my particular problem. I feel like, this is a dumb question to ask, but I'm stuck here, so need help.
Thanks..
(Please feel free to suggest corrections.)
You have access to self.request.user in the form_valid of the view. But in order to limit the choices in the form you have to customize the form before it is served initially. You best override the view's get_form and set the form field's queryset:
class B_Create(CreateView):
model = B
fields = ['b_a','b_details']
def get_form(self, *args, **kwargs):
form = super(B_Create, self).get_form(*args, **kwargs)
form.fields['b_a'].queryset = self.request.user.a_set.all()
# form.fields['b_a'].queryset = A.objects.filter(a_user=self.request.user)
return form
Generally, there are three places where you can influence the choices of a ModelChoiceField:
If the choices need no runtime knowledge of your data, user, or form instance, and are the same in every context where a modelform might be used, you can set limit_choices_to on the ForeignKey field itself; as module level code, this is evaluated once at module import time. The according query will be built and executed every time a form is rendered.
If the choices need no runtime knowledge, but might be different in different forms, you can use custom ModelForms and set the queryset in the field definition of the respective form field.
If the queryset needs any runtime information, you can either override the __init__ of a custom form and pass it any information it needs to set the field's queryset or you just modify the queryset on the form after it is created which often is a quicker fix and django's default views provide nice hooks to do that (see the code above).
The #schwobaseggl answer is excellent.
Here is a Python 3 version. I needed to limit the projects dropdown input based on the logged-in user.
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
template_name = 'brand/product-create.html'
fields = '__all__'
def get_form(self, form_class=None):
form = super().get_form(form_class=None)
form.fields['project'].queryset = form.fields['project'].queryset.filter(owner_id=self.request.user.id)
return form
I am looking for a way to properly ovverride the default .create() method of a ModelSerializer serializer in Django Rest Framework for dealing with an extra parameter.
In my original Django model I have just overridden the default.save() method for managing an extra param. Now .save() can be called also in this way: .save(extra = 'foo').
I have to create a ModelSerializer mapping on that original Django model:
from OriginalModels.models import OriginalModel
from rest_framework import serializers
class OriginalModelSerializer(serializers.ModelSerializer):
# model fields
class Meta:
model = OriginalModel
But in this way I can't pass the extra param to the model .save() method.
How can I properly override the .create() method of my OriginalModelSerializer class to take (eventually) this extra param into account?
Hmm. this might not be the perfect answer given I don't know how you want to pass this "extra" in (ie. is it an extra field in a form normally, etc)
What you'd probably want to do is just represent foo as a field on the serializer. Then it will be present in validated_data in create, then you can make create do something like the following
def create(self, validated_data):
obj = OriginalModel.objects.create(**validated_data)
obj.save(foo=validated_data['foo'])
return obj
You'd probably want to look at the default implementation of create for some of the other things it does though (like remove many-to-many relationships, etc.).
You can now do this in the view set (threw in user as a bonus ;) ):
class OriginalModelViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows OriginalModel classes to be viewed or edited.
"""
serializer_class = OriginalModelSerializer
queryset = OriginalModel.objects.all()
def perform_create(self, serializer):
user = None
if self.request and hasattr(self.request, "user"):
user = self.request.user
serializer.save(user=user, foo='foo')
That way the Serializer can stay generic, i.e.:
class OriginalModelSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = OriginalModel
fields = '__all__'
I have a model with a foreign key to group (the other fields don't matter):
class Project(models.Model) :
group = models.ForeignKey(Group)
...
I have a model form for this model:
class AddProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ["group","another"]
In my urls, I am using this in a generic view:
(r'^$', create_object, {'form_class':AddProjectForm, 'template_name':"form.html", 'login_required':True, 'extra_context':{'title':'Add a Project'}}),
That all works, but I want to have the group field display only the groups that the current user belongs to, not all of the groups available. I'd normally do this by passing in the user to the model form and overriding init if I wasn't in a generic view. Is there any way to do this with the generic view or do I need to go with a regular view to pass in that value?
This is gonna look dirty, since the generic view instantiates the form_class with no parameters. If you really want to use the generic_view you're gonna have to generate the class dynamically :S
def FormForUser(user):
class TmpClass(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(TmpClass, self).__init__(*args, **kwargs)
self.fields['group'].queryset = user.group_set.all()
class Meta:
model = Project
fields = ['group', 'another']
Then wrap the create object view
#login_required # Only logged users right?
def create_project(request):
user = request.user
form_class = FormForUser(user)
return create_object(request, form_class=form_class, ..... )
My recommendation is to write your own view, it will give you more control on the long term and it's a trivial view.
No, you'll need to make a regular view. As can be seen by looking at the source code for create_object(), there's no functionality to pass in extra parameters to the modelform (in django 1.2):
http://code.djangoproject.com/svn/django/branches/releases/1.2.X/django/views/generic/create_update.py
let's assume that I have very basic model
class Message(models.Model):
msg = models.CharField(max_length=30)
this model is registered with admin module:
class MessageAdmin(admin.ModelAdmin):
pass
admin.site.register(Message, MessageAdmin)
Currently when I go into the admin interface, after clicking "Add message" I have only one form where I can enter the msg.
I would like to have multiple forms (formset perhaps) on the "Add page" so I can create multiple messages at once. It's really annoying having to click "Save and add another" every single time.
Ideally I would like to achieve something like InlineModelAdmin but it turns out that you can use it only for the models that are related to the object which is edited.
What would you recommend to use to resolve this problem?
This may not be exactly what you are looking for, but if you want to create multiple objects at the same time you could to somehthing like this:
#In /forms.py
MessageAdminForm(forms.ModelForm):
msg = CharField(max_length=30)
count = IntegerField()
#In /admin.py
from app.admin import MessageAdminForm
MessageAdmin(admin.ModelAdmin):
form = MessageAdminForm
fieldsets = (
(None, {
'fields' : ('msg','count')
}),)
def save_model(self, request, obj, form, change):
obj.msg = form.cleaned_data['msg']
obj.save()
for messages in range(form.cleaned_data['count']):
message = Message(msg=form.cleaned_data['msg'])
message.save()
Basicly what you are doing is creating a custom form for your admin template, which ask the user how many times the object shall be created. The logic is than interpreted in the save_model method.
As a workaround, Since, It is likely that you have a FK to User, so you could define an InlineModel on the User model.
Otherwise, the easiest approach may be to create a custom admin view since, there isn't a generic admin view that displays and saves formsets.
This is easy if you are using an Inline. Then you could use extra = 10 or however many extra formsets you want. There doesn't seem to be an equivalent for the ModelAdmin.
Of course in your messages model you would need to create a ForeignKey to some sort of message grouping model as another layer of function and to get the multi-formset layout that you are looking for.
For example:
models.py:
class Group(models.Model):
name = models.CharField(max_length=30)
class Message(models.Model):
msg = models.CharField(max_length=30)
grp = models.ForeignKey(Group)
admin.py:
class MessageInline(admin.TabularInline):
model = Message
extra = 10
class GroupAdmin(admin.ModelAdmin):
inlines = [MessageInline]
admin.site.register(Group, GroupAdmin)
This would give you what you want in the Admin view and create grouping (even if you only allow for one group) and the only extra field would be the name in the group model. I am not even sure you would need that. Also I am sure the value for extra could be generated dynamically for an arbitrary value.
I hope this helps!