Hello I cant seem to filter a Foreignkey Dropdown within an Inline form.
These are my classes:
class Author(models.Model):
name = models.CharField(max_length=50)
desc = models.CharField(max_length=50)
class Book(models.Model):
author = models.ForeignKey(Author)
title= models.CharField(max_length=50)
class BookPrio::
author = models.ForeignKey(Author)
book = models.ForeignKey(Book)
prio = models.IntegerField()
my admin.py looks like:
class BookPrioInline(admin.TabularInline):
model = BookPrio
class AuthorAdmin(admin.ModelAdmin):
inlines =(BookPrioInline,)
admin.site.register(Author, AuthorAdmin)
I want the Books dropdown on the BookPrio inline to be filter on the selected Author in the admin panel. But can;t find out how to do it.
Some help would be welcome
I'm a little confused by your question but found it interesting.
You want the author dropdown on the inlines to be the selected author -- so the inline will always only have 1 choice, the current author?
Well, normally you'd use formfield_for_foreignkey
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
But you have a special case where each inline depends on the object being edited.
I didn't see any easy ways to access the edited objects so I put the formfield_for_foreignkey definition in the change_view, and assigned the inlines from within the view function.
class BookPrioInline(admin.TabularInline):
model = BookPrio
class AuthorAdmin(admin.ModelAdmin):
inlines = (BookPrioInline,)
def change_view(self, request, object_id, extra_context=None):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'book':
kwargs['queryset'] = Book.objects.filter(author__id=object_id)
return super(ItemInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
ItemInline.formfield_for_foreignkey = formfield_for_foreignkey
self.inline_instances = [ItemInline(self.model, self.admin_site)]
return super(AuthorAdmin, self).change_view(request, object_id,
extra_context=extra_context)
admin.site.register(Author, AuthorAdmin)
Related
I'm new to programming and my first language/stack is Python and Django. I have figured out how to create a dropdown menu in my Script form that is pointing to a different class "Patient" but I can't figure out how to only show me data that the current user created. I'm confused if I should set this in my models.py, forms.py or in the views.py? Here is what I have that I think should be working but it is not. (Tried setting in the views.py)
Models.py
class Patient(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient_name = models.CharField(max_length=40, unique=True)
def __str__(self):
return self.patient_name
class Script(models.Model):
author = models.ForeignKey(get_user_model(), on_delete=models.CASCADE,)
patient = models.ForeignKey(Patient, on_delete=models.CASCADE, verbose_name='Primary Patient')
So my patient field is my dropdown and it is looking at the Patient class grabbing the patient name string. I only want patient_name entry's that this user created in the dropdown.
Views.py
class ScriptCreateView(LoginRequiredMixin, CreateView):
model = Script
template_name = 'script_new.html'
success_url = reverse_lazy('script_list')
fields = (
'patient',
'drug_name',
'drug_instructions',
'drug_start_day',
'drug_start_time',
'drug_hours_inbetween',
'drug_num_days_take',
)
#This sets user created fields only??
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)
#This sets the author ID in the form
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form
)
Forms.py
class ScriptForm(forms.ModelForm):
class Meta:
model = Script
fields = '__all__'
#This is requiring user login for any of these views??
def __init__(self, user=None, *args, **kwargs):
super().__init__(*args, **kwargs)
if user:
self.fields['patient'].queryset = Patient.objects.filter(author=user)
I'm sure it is my lack of experience here but I thought by setting the function def get_queryset in the view that it would only show me user created data. I have googled a bunch and I really can't find the clear answer on this.
In your views.py file initialize form like this please
<form or form_class> = Form(request.POST, user=request.user)
I had to add the last form.fields query below in the view which filtered items only created by "author" which is what I was looking for:
def get_form(self):
form = super().get_form()
form.fields['drug_start_day'].widget = DatePickerInput()
form.fields['drug_start_time'].widget = TimePickerInput()
form.fields['patient'].queryset = Patient.objects.filter(author=self.request.user)
return form
In the admin panel, I can add Persons to my CompleteClass model. There is a M2M relationship between CompleteClass and Person. But, my form doesn't work as it should. The pub_date will update, and I can save the head_count, but not the ModelMultipleChoiceField (persons) -- it will not save.
models.py
class Person(models.Model):
name = models.CharField(max_length=255)
persona_description = models.CharField(max_length=255)
def __str__(self):
return self.name
class CompleteClass(models.Model):
persons = models.ManyToManyField(Person)
class_name = models.CharField(max_length=255)
class_head_count = models.IntegerField()
class_pub_date = models.DateField()
def __str__(self):
return '%s %s' % (self.class_name, self.class_head_count)
def save_complete_class(self):
self.class_pub_date = timezone.now()
self.save()
class Meta:
ordering = ('class_pub_date',)
Here is views.py:
def class_new(request):
if request.method == "POST":
form = CompleteClassForm(request.POST)
if form.is_valid():
complete_class = form.save(commit=False)
complete_class.class_pub_date = timezone.now()
complete_class.save()
form.save_m2m()
return redirect('class_detail', pk=complete_class.pk)
else:
form = CompleteClassForm()
return render(request, 'app/class_edit.html', {'form': form})
and forms.py
class CompleteClassForm(forms.ModelForm):
class Meta:
model = CompleteClass
fields = ('class_name', 'class_head_count',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super(CompleteClassForm, self).__init__(*args, **kwargs)
self.fields['class_persons']=forms.ModelMultipleChoiceField(queryset=Person.objects.all())
I've read through the documentation and used the save_m2m since i've set commit=false.
The POST data contains person data, but it's not being written to the database. I'm stumped. Please help!
Only fields named in the fields tuple are saved to the instance. You don't have your m2m field listed there.
You also define your modelchoicefield with a different name - class_persons instead of persons. In fact, there is no reason to define that field separately at all - you haven't changed any of the attributes from the defaults.
And once you've removed that definition, there is also no reason to override __init__, seeing as you never pass the user parameter nor do you use it anywhere in the form.
I want to get the id or pk of a ForeignKey relationship post_comment but I've tried many different ways to catch it and i do not have any good result, please guys give me a hand in this situation
In views.py
class createComment(View):
form_class = CommentForm
template_name = "createComment.html"
def get(self, request):
form = self.form_class(None)
return render(request, self.template_name, {'form':form})
def post(self, request):
obj = self.form_class(None)
obj.title_comment = self.request.POST['title_comment']
obj.body_comment = self.request.POST['body_comment']
obj.post_comment = self.pk
obj.save()
In models.py
class Comment(models.Model):
user_comment = models.ForeignKey("auth.User")
title_comment = models.CharField(max_length=50)
body_comment = models.TextField()
timestamp_comment = models.DateTimeField(auto_now=True)
post_comment = models.ForeignKey("Post", null=True)
status_comment = models.BooleanField(default=True)
def __unicode__(self):
return unicode(self.title_comment)
def __str__(self):
return self.title_comment
You can pass a primary key in the url, and then use it in your class as one way.
kwargs.get(pk name)
You could change post to:
def post(self, request, *args, **kwargs)
You then can't just assign obj.post_comment = kwargs.get(pk) you have to actually get the object.
Post.objects.get(pk = pk)
You might want to also consider renaming fieldname_comment to just fieldname for your models fields. Seems a bit redundant to have _comment on every single field in the Comment model.
I don't know how works class based views but I can tell you that self.pk does not exist in class based view, you would try get form instance and get the I'd field from this instance...
models.py:
class Tag(models.Model):
name = models.CharField(max_length=100)
description = models.CharField(max_length=500, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now_add=True)
class Post(models.Model):
user = models.ForeignKey(User)
tag = models.ManyToManyField(Tag)
title = models.CharField(max_length=100)
content = models.TextField()
created = models.DateTimeField(default=datetime.datetime.now)
modified = models.DateTimeField(default=datetime.datetime.now)
def __unicode__(self):
return '%s,%s' % (self.title,self.content)
class PostModelForm(forms.ModelForm):
class Meta:
model = Post
class PostModelFormNormalUser(forms.ModelForm):
class Meta:
model = Post
widgets = { 'tag' : TextInput() }
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
views.py:
if request.method == 'POST':
form = PostModelFormNormalUser(request.POST)
print form
print form.errors
tagstring = form.data['tag']
splitedtag = tagstring.split()
if form.is_valid():
temp = form.save(commit=False)
temp.user_id = user.id
temp.save()
l = len(splitedtag)
for i in range(l):
obj = Tag(name=splitedtag[i])
obj.save()
post.tag_set.add(obj)
post = Post.objects.get(id=temp.id)
return HttpResponseRedirect('/viewpost/' + str(post.id))
else:
form = PostModelFormNormalUser()
context = {'form':form}
return render_to_response('addpost.html', context, context_instance=RequestContext(request))
Here form.is_valid() is always false because it gets the tag as string from form. But it expects list as form.data['tag'] input. Can anyone tell me how can i fix it?
How can i write a custom widget to solve this?
I don't think you need a custom widget (you still want a TextInput), you want a custom Field. To do this, you should subclass django.forms.Field. Unfortunately the documentation is scant on this topic:
If the built-in Field classes don’t meet your needs, you can easily create custom Field classes. To do this, just create a subclass of django.forms.Field. Its only requirements are that it implement a clean() method and that its init() method accept the core arguments mentioned above (required, label, initial, widget, help_text).
I found this blog post that covers both custom widgets and fields in more depth. The author disagrees with the documentation I quoted above - it's worth reading over.
For your specific situation, you would do something like this (untested):
class MyTagField(forms.Field):
default_error_messages = {
'some_error': _(u'This is a message re: the somr_error!'),
}
def to_python(self, value):
# put code here to coerce 'value' (raw data from your TextInput)
# into the form your code will want (a list of Tag objects, perhaps)
def validate(self, value):
if <not valid for some reason>:
raise ValidationError(self.error_messages['some_error'])
Then in your ModelForm:
class PostModelFormNormalUser(forms.ModelForm):
tag = MyTagField()
class Meta:
model = Post
exclude = ('user', 'created', 'modified')
def __init__(self, *args, **kwargs):
super(PostModelFormNormalUser, self).__init__(*args, **kwargs)
self.fields['tag'].help_text = None
class Foo(models.Model):
title = models.TextField()
userid = models.IntegerField()
image = models.CharField(max_length=100)
def __unicode__(self):
return self.title
class Bar(models.Model):
foo = models.ForeignKey(Foo, related_name='Foo_picks', unique=True)
added_on = models.DateTimeField(auto_now_add=True)
In Django admin add_view:
def add_view(self, *args, **kwargs):
self.exclude = ("added_on",)
self.readonly_fields = ()
return super(Bar, self).add_view(*args, **kwargs)
So, Field shows in the admin add view is foo Which is a drop down list and shows all the titles. Some title of Foo remains empty or ''. So, drop down list have lots of empty value because it title is empty. I want to filter out those empty values.
You can provide your own form for ModelAdmin, with custom queryset for foo field.
from django import forms
from django.contrib import admin
#Create custom form with specific queryset:
class CustomBarModelForm(forms.ModelForm):
class Meta:
model = Bar
fields = '__all__'
def __init__(self, *args, **kwargs):
super(CustomBarModelForm, self).__init__(*args, **kwargs)
self.fields['foo'].queryset = Foo.objects.filter(title__isnull=False)# or something else
# Use it in your modelAdmin
class BarAdmin(admin.ModelAdmin):
form = CustomBarModelForm
Something like this...
docs
for django 1.6:
For foreign key:
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#ModelAdmin.formfield_for_foreignkey
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "title":
kwargs["queryset"] = Foo.objects.filter(title__isnull=False)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
I stumbled across this question when looking for a solution to filter dropdown options on the fly in the admin interface based on the selection in another field -- not based on a pre-filtered list at page load. The solution I found was this library: https://github.com/digi604/django-smart-selects which is an app that uses ajax calls and allows chain filtering to multiple levels. Works like a charm for me. -HTH
You could subclass your own model.ModelAdmin and create a custom field for your ChoiceField...
class CustomForm(model.ModelForm):
class Meta:
model = Foo
foo = forms.ChoiceField(widget=forms.Select, initial=self.foo_queryset)
def foo_queryset(self):
return Foo.objects.filter(xy)...
class FooAdmin(model.ModelAdmin):
form = CustomForm