Adding 2 models inside a Admin Manager - python

I have 2 models, Question and Image. I want to create a manager in django admin, to include the fields of Image inside the Question admin panel.
These are the models:
class Question(models.Model):
quiz = models.ManyToManyField(Quiz, blank=True, )
category = models.ForeignKey(Category, blank=True, null=True, )
content = models.CharField(max_length=1000,
blank=False,
help_text="Enter the question text that you want displayed",
verbose_name='Question',
)
explanation = models.TextField(max_length=2000,
blank=True,
help_text="Explanation to be shown after the question has been answered.",
verbose_name='Explanation',
)
class Meta:
verbose_name = "Question"
verbose_name_plural = "Questions"
ordering = ['category']
def __unicode__(self):
return self.content
class Image(models.Model):
TYPE_CHOICES = (
('A','Answer'),
('Q','Question'),
)
image = models.ImageField(upload_to='static/img')
type = models.CharField(max_length=1, choices=TYPE_CHOICES)
question = models.ForeignKey(Question, blank=True, null=True)
answer = models.ForeignKey(Answer, blank=True, null=True)
def __unicode__(self):
return self.type
This is the Question Manager in Django Admin:
class QuizAdminForm(forms.ModelForm):
class Meta:
model = Quiz
questions = forms.ModelMultipleChoiceField(
queryset=Question.objects.all(),
required=False,
widget=FilteredSelectMultiple(verbose_name=('Questions'),
is_stacked=False )
)
def __init__(self, *args, **kwargs):
super(QuizAdminForm, self).__init__(*args, **kwargs)
if self.instance.pk:
self.fields['questions'].initial = self.instance.question_set.all()
def save(self, commit=True):
quiz = super(QuizAdminForm, self).save(commit=False)
if commit:
quiz.save()
if quiz.pk:
quiz.question_set = self.cleaned_data['questions']
self.save_m2m()
return quiz

You are looking InlineModelAdmin models.
class ImageInline(admin.TabularInline):
model = Image
...
class QuestionAdmin(admin.ModelAdmin):
list_display = ('content', 'category', )
list_filter = ('category',)
fields = ('content', 'category', 'quiz', 'explanation')
search_fields = ('content', 'explanation')
filter_horizontal = ('quiz',)
inlines = [AnswerInline, ImageInline]
https://docs.djangoproject.com/en/1.6/ref/contrib/admin/#inlinemodeladmin-objects
Good to see you are using Django Quiz app. I have recently added a lot of changes to it and it would be good if you could contribute anything to the repo:
https://github.com/tomwalker/django_quiz

Related

ModelMultipleChoiceField returns queryset instead of object instance

Hello I have this problem with multiple checkbox select
I have this form
class AnswerForm(forms.ModelForm):
class Meta:
model = Response
fields = ('answer', )
def __init__(self, *args, **kwargs):
question = kwargs.pop('question')
super().__init__(*args, **kwargs)
self.fields['answer'].queryset = question.answers.order_by('text')
if question.question_field_type == 'multiple':
self.fields['answer'] = forms.ModelMultipleChoiceField(
widget=forms.CheckboxSelectMultiple(
attrs={'autocomplete': 'off'}),
queryset=question.answers.order_by('text'),
)
My problem is that on submission it raises this error
"<QuerySet [<Answer: A. High School Diploma>, <Answer: B. Associate's Degree>]>": "Response.answer" must be a "Answer" instance.
how do make these to return answer instance instead of a queryset.
model
class Question(models.Model):
RADIO = 'radio'
SELECT = 'select'
TEXT = 'text'
MULTI = 'multiple'
QUESTION_TYPES = (
(RADIO, 'radio'),
(SELECT, 'select'),
(TEXT, 'text'),
(MULTI, 'multiple'),
)
text = models.CharField(
max_length=512,
help_text="this text will be displayed to the user taking the survey"
)
question_number = models.PositiveIntegerField(
default=0,
help_text='Use for question ordering, by default questions are ordered by text'
)
partner_related_question = models.BooleanField(
default=False,
help_text='this will be used to match ideal partner against user choices'
)
question_field_type = models.CharField(
choices=QUESTION_TYPES, max_length=15)
created_date = models.DateField(auto_now_add=True)
objects = QuestionManager()
class Meta:
ordering = ['question_number']
def __str__(self):
return self.text
class Answer(models.Model):
question = models.ForeignKey(
Question, on_delete=models.CASCADE, related_name='answers')
text = models.CharField('Answer', max_length=512)
def __str__(self):
return self.text
class Response(models.Model):
person = models.ForeignKey(
Person, on_delete=models.CASCADE, related_name='person_answers')
answer = models.ForeignKey(
Answer, on_delete=models.CASCADE, related_name='+')
created = models.DateTimeField(default=now, editable=False)
I think you should use ManyToManyField in Response class instead of ForeignKey:
class Response(models.Model):
...
answers = models.ManyToManyField(Answer)
...
# forms.py
class AnswerForm(forms.ModelForm):
class Meta:
model = Response
fields = ('answers', )
then in the case you are using CBV CreateView, override form_valid as this:
def form_valid(self, form):
self.object = form.save()
for answer in form.cleaned_data['answers']:
answer.save()
self.object.answers.add(answer)
return HttpResponseRedirect(self.get_success_url())
remember to add the get_success_url() method to Response model

How to make autocomplete filter in django admin form with class based model

I want to add a dropdown with autocomplete filter such as select2 into django admin form with class based model. i have tried several tricks avilable over the internet but not succeeded. here are some code snippet i have. i want to show all category for a post which is already available into model.
in my model.py
class Post(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
title = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True)
featured_image = models.ImageField(null=True, blank=True, upload_to="blog/", verbose_name='Featured Image')
author = models.ForeignKey(User, on_delete= models.CASCADE,related_name='blog_posts')
updated_on = models.DateTimeField(auto_now= True)
content = RichTextUploadingField()
created_on = models.DateTimeField(auto_now_add=True)
status = models.IntegerField(choices=STATUS, default=0)
class Meta:
ordering = ['-created_on', 'title']
def __str__(self):
return self.title
def _generate_slug(self):
value = self.title
slug_candidate = slugify(value, allow_unicode=True)
self.slug = slug_candidate
def save(self, *args, **kwargs):
if not self.pk:
self._generate_slug()
super().save(*args, **kwargs)
my admin.py
class PostAdmin(admin.ModelAdmin):
list_display = ('title', 'slug', 'status', 'category', 'author','created_on')
list_filter = ("status",)
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
actions = [export_as_csv_action("CSV Export", fields=['title','slug','author','featured_image','status','created_on','updated_on'])]
how my form looks into django-admin
please suggest anything how to add i filter for category dropdown filter with autocomplete.
In Django 2.0+ you can just add autocomplete_fields to the ModelAdmin:
class PostAdmin(admin.ModelAdmin):
autocomplete_fields = ['category']
This will add asynchronous search of all categories the user has access to (versus a standard dropdown/select).

How to add different model to ModelForm as extra field?

So I'm trying to add an extra field to a ModelForm class in the __init__ func, so that on the Create/Update class views I could grab this model and create a new model. Since the Model doesn't have this field, how could I add it in the __init__ func or is there some other way I could add this field?
I've tried overriding __init__ method to include this field in the ModelForm class, but this still throws me an error that the argument is unexpected
class MeterInstallationForm(ModelForm):
class Meta:
model = MeterInstallation
fields = METER_INSTALLATION_DEFAULT_FIELDS
def __init__(self, *args, **kwargs):
instance = kwargs.get("instance")
super(MeterInstallationForm, self).__init__(*args, **kwargs)
if instance:
# get tariff info in the form
# self.fields["tariff"] = instance.tariff
self.fields["tariff"] = TariffApplication.objects.filter(meter_installation__pk=instance.meter_installation.pk).values_list("tariff", flat=True)
class MeterInstallationCreateView(MeterInstallationsViewMixin, LoginRequiredMixin, CreateView):
template_name = "meter_installations/meter_installation_create.html"
fields = (
"name",
"meter_type",
"parent",
"meter",
"building",
"initial_reading",
"final_reading",
"active_after",
"active_until",
"comment",
)
form_class = MeterInstallationForm
meter_installation_create_view = MeterInstallationCreateView.as_view()
class MeterInstallation(ActiveAfterUntilModel, DateTrackedModel, MPTTModel, NamedModel): # type: ignore
meter_type = models.ForeignKey(
MeterType,
on_delete=models.PROTECT,
null=False,
blank=False,
related_name="installations",
verbose_name=_("Meter Installation type"),
)
parent = TreeForeignKey(
"self", on_delete=models.CASCADE, null=True, blank=True, related_name="children", db_index=True
)
meter = models.ForeignKey(
Meter, on_delete=models.PROTECT, related_name="installations", null=False, blank=False, verbose_name=_("Meter")
)
building = models.ForeignKey(
Building,
on_delete=models.PROTECT,
related_name="meter_installations",
null=True,
blank=False,
verbose_name=_("Building"),
)
places = models.ManyToManyField(Place, related_name="meter_installations", blank=False, verbose_name=_("Places"))
initial_reading = models.DecimalField(
decimal_places=4, max_digits=10, null=False, blank=False, default=0, verbose_name=_("Initial reading")
)
final_reading = models.DecimalField(
decimal_places=4, max_digits=10, null=True, blank=True, default=0, verbose_name=_("Final reading")
)
class MPTTMeta:
order_insertion_by = ["meter"]
def get_absolute_url(self):
return reverse("meter-installations:meter-installation-detail", kwargs={"pk": self.pk})
def delete(self, *args, **kwargs):
first_lvl_children = self.get_children().filter(level=1)
for first_lvl_child in first_lvl_children:
first_lvl_child.parent = None
first_lvl_child.save()
for leaf in first_lvl_child.get_children():
leaf.parent = first_lvl_child
leaf.save()
tree_id = first_lvl_child.tree_id
MeterInstallation.objects.partial_rebuild(tree_id)
super(MeterInstallation, self).delete(*args, **kwargs)
def __str__(self):
return f"[{self.pk}] type: {self.meter_type_id}, meter: {self.meter_id}"
class Tariff(ActiveAfterUntilModel, NamedModel, DateTrackedModel):
tariff_type = models.ForeignKey(
MeterType,
on_delete=models.PROTECT,
null=False,
blank=False,
related_name="tariffs",
verbose_name=_("Tariff type"),
)
building = models.ForeignKey(
Building, on_delete=models.PROTECT, related_name="tariffs", null=True, blank=False, verbose_name=_("Building")
)
unit_name = models.CharField(max_length=100, null=False, blank=True, unique=False, verbose_name=_("Unit name"))
unit_price = models.DecimalField(
decimal_places=4, max_digits=10, null=False, blank=False, default=0.0, verbose_name=_("Unit price")
)
VAT = models.DecimalField(decimal_places=2, max_digits=10, null=True, blank=True, verbose_name=_("VAT"))
class Meta:
unique_together = ("name", "tariff_type", "active_after", "active_until")
def get_absolute_url(self):
return reverse("tariffs:tariff-detail", kwargs={"pk": self.pk})
def __str__(self) -> str:
return (
f"[{self.pk}] "
f"type: {self.tariff_type_id}, "
f"building: {self.building_id}, "
f"price: {self.unit_price}, "
f"VAT: {self.VAT}, "
f"active_until: {self.active_until}"
)
class TariffApplication(ActiveAfterUntilModel, DateTrackedModel): # type: ignore
tariff = models.ForeignKey(
Tariff,
on_delete=models.PROTECT,
null=False,
blank=False,
related_name="tariff_applications",
verbose_name=_("Tariff Applications"),
)
meter_installation = models.ForeignKey(
"MeterInstallation",
on_delete=models.PROTECT,
null=False,
blank=False,
related_name="tariff_applications",
verbose_name=_("Meter Installation"),
)
def __str__(self) -> str:
return f"[{self.pk}] tariff: {self.tariff_id}, meter installation: {self.meter_installation_id}"
I would love to know how to make this work, so that in my CreateView I could start creating a third model by the given Tariff
You can use a ModelChoiceField so that you can select a tariff on the form.
class MeterInstallationForm(ModelForm):
class Meta:
model = MeterInstallation
fields = METER_INSTALLATION_DEFAULT_FIELDS
def __init__(self, *args, **kwargs):
super(MeterInstallationForm, self).__init__(*args, **kwargs)
self.fields["tariff"] = forms.ModelChoiceField(queryset=Tariff.objects.all())
Then in your form_valid() method, you can retrieve the tariff from cleaned_data and create the related TariffApplication.
def form_valid(self, form):
instance = form.save()
TariffApplication.objects.create(tariff=form.cleaned_data['tariff'], meter_installation=instance)
return HttpResponseRedirect(self.get_success_url())
You may need to change the queryset if you need to filter the list of available tariffs. In your original question, I don't think it makes sense to have if instance in the form's __init__ method, because an instance won't be passed to the form for a CreateView.
You can include extra field in your ModelForm and set that in your view such as:
# forms.py
class MeterInstallationForm(ModelForm):
class Meta:
model = MeterInstallation
fields = METER_INSTALLATION_DEFAULT_FIELDS + ('tariff',)
# views.py
class MeterInstallationCreateView(MeterInstallationsViewMixin, LoginRequiredMixin, CreateView):
template_name = "meter_installations/meter_installation_create.html"
fields = (
"name",
"meter_type",
"parent",
"meter",
"building",
"initial_reading",
"final_reading",
"active_after",
"active_until",
"comment",
)
form_class = MeterInstallationForm
def form_valid(self, form):
form.instance.tariff = forms.TariffApplication(form.data)
return super().form_valid(form)

How can i add a field to my Django model to dynamically filter a dropdown on another field?

I have a created Django-CMS Plugin with a ManytoManyField to another model. When creating the Plugin on the Front-End, i want the user to be able to filter the ManytoManyField list (which might be too long).
By the way this future is already on Django admin:
class ModelAdmin(admin.ModelAdmin):
list_filter = ('field_name', )
form = PartnerLogoTableForm
Is it possible to have something similar like that on my
cms_plugins.py:
class PartnerLogoTablePlugin(CMSPluginBase):
model = LogoTablePlugin
form = LogoTablePluginForm
name = _('LogoTable Plugin')
render_template = False
search_fields = ('description',)
def render(self, context, instance, placeholder):
self.render_template = 'aldryn_logo_tables/plugins/%s/logotable.html' % instance.style
context.update({
'object': instance,
'placeholder': placeholder,
})
return context
plugin_pool.register_plugin(PartnerLogoTablePlugin)
models.py:
class PartnerLogoTable(models.Model):
name = models.CharField(_('Partner name'), max_length=255)
image = FilerImageField(verbose_name=_('Image'), null=True, blank=True, on_delete=models.SET_NULL)
partner_url = models.TextField(_('Partner url'), null=True, blank=True, validators=[URLValidator()])
is_active = models.BooleanField(_('Is active'), blank=True, default=True)
created_at = models.DateTimeField(_('Created at'), auto_now_add=True)
updated_at = models.DateTimeField(_('Updated at'), auto_now=True)
order = models.IntegerField(_('Order'), null=True, blank=True)
class Meta:
verbose_name = _('Partner Logo')
verbose_name_plural = _('Partner Logos')
ordering = ['order']
def __str__(self):
return self.name
class LogoTablePlugin(CMSPlugin):
DEFAULT = 'default'
LOGOTABLE_CHOICES = [
(DEFAULT, _('Default')),
]
description = models.CharField(_('Description'), max_length=255, null=True, blank=True)
partner = models.ManyToManyField(PartnerLogoTable, verbose_name=_('Partner'))
logo_per_row = models.IntegerField(_('Logo per line'), default=1, null=True, blank=True,
validators=[MaxValueValidator(4), MinValueValidator(1)],
help_text=_('Number of logos to be displayed per row'))
style = models.CharField(_('Style'), choices=LOGOTABLE_CHOICES + get_additional_styles(), default=DEFAULT,
max_length=50, blank=True, null=True, )
class Meta:
verbose_name = _('Partner Logo Plugin')
verbose_name_plural = _('Partner Logo Plugins')
def __unicode__(self):
return u'%s' % self.description
forms.py
class LogoTablePluginForm(forms.ModelForm):
model = LogoTablePlugin
def clean_style(self):
.....
return style
class PartnerLogoTableForm(forms.ModelForm):
model = PartnerLogoTable
def clean(self):
....
return self.cleaned_data
This is how the plugin looks now
ModelSelect2Multiple from django-autocomplete-light seems perfect for your use case.

django import-export "NOT NULL constraint failed"

I'm pretty new at Django and am stuck trying to implement Django import-export. My three pertinent model are Officer, Incident, and Details. Officer and Incident are in a M2M relationship through Details. I have gotten all of the functionality to work besides importing details. When I try to import via the import button, I get "NOT NULL constraint failed: police_archive_details.incident_id" for each row in the .xls or .csv file I'm importing.
Here's my (current) admin.py
from django.contrib import admin
from import_export import resources, widgets, fields
from import_export.admin import ImportExportModelAdmin, ImportExportActionModelAdmin
from forms import AdminTextForm, OfficerTextForm
from .models import Officer, Incident, Details, SiteText
class FullNameForeignKeyWidget(widgets.ForeignKeyWidget):
def get_queryset(self, value, row):
return self.model.objects.filter(
first_name__iexact=row["first_name"],
last_name__iexact=row["last_name"]
)
class DetailsInlineAdmin (admin.TabularInline):
model = Details
extra = 5
class OfficerResource(resources.ModelResource):
class Meta:
model = Officer
class OfficerAdmin(ImportExportModelAdmin):
list_display = ('first_name', 'last_name', 'badge', 'department')
search_fields = ['first_name', 'last_name']
inlines = [DetailsInlineAdmin]
resource_class = OfficerResource
form=OfficerTextForm
class Media:
js = ('//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js','/static/admin/js/admin/popup.js')
class IncidentResource(resources.ModelResource):
officer = fields.Field(
column_name='officer',
attribute='officer',
widget=widgets.ForeignKeyWidget(Officer, 'badge'))
class Meta:
fields = ('officer',)
model = Incident
class IncidentAdmin(ImportExportModelAdmin):
list_display = ('office','case_number')
search_fields = ['case_number']
inlines = [DetailsInlineAdmin]
resource_class = IncidentResource
class DetailsResource(resources.ModelResource):
officer = fields.Field(
column_name='officer',
attribute='officer',
widget=FullNameForeignKeyWidget(Officer))
incident = fields.Field(
column_name='incident',
attribute='incident',
widget=widgets.ForeignKeyWidget(Incident, 'case_number'))
class Meta:
fields = ('id','incident','officer__last_name','officer__first_name','allegation', 'finding', 'action')
model = Details
class DetailsAdmin(ImportExportModelAdmin):
list_display=('incident','officer', 'allegation', 'finding', 'action')
search_fields = ['officer__last_name', 'incident__case_number']
resource_class = DetailsResource
class SiteTextAdmin(admin.ModelAdmin):
form=AdminTextForm
admin.site.register(Officer, OfficerAdmin)
admin.site.register(Incident, IncidentAdmin)
admin.site.register(Details, DetailsAdmin)
admin.site.register(SiteText, SiteTextAdmin)
And here's models.py
from __future__ import unicode_literals
from django.db import models
from tinymce import models as tinymce_models
class Officer(models.Model):
first_name = models.CharField(max_length=80, blank=True, null=True)
last_name = models.CharField(max_length=80, blank=True, null=True)
badge = models.IntegerField(blank=True, null=True)
department = models.CharField(max_length=50, blank=True, null=True)
model_pic = models.ImageField(upload_to = "police_archive/officer_photos", default= 'noimage', blank=True, null=True)
description = tinymce_models.HTMLField(blank=True, null=True)
def __str__(self):
return self.last_name + ', ' + self.first_name
class Meta():
ordering = ['last_name']
class Incident(models.Model):
officer = models.ManyToManyField(Officer, through='Details')
case_number = models.CharField(max_length=50, blank=True)
OFFICE_CHOICES = (
('CRA', 'Civilian Review Authority'),
('IA', 'Internal Affairs'),
('OPCR', 'Office of Police Conduct Review'),
)
office = models.CharField(max_length=10,
choices=OFFICE_CHOICES,
)
def __str__(self):
return self.case_number
class Meta():
ordering = ['-case_number']
class Details(models.Model):
officer = models.ForeignKey(Officer, on_delete=models.CASCADE, blank=True)
incident = models.ForeignKey(Incident, on_delete=models.CASCADE, blank=True)
allegation = models.CharField(max_length=50, blank=True)
finding = models.CharField(max_length=50, blank=True)
action = models.CharField(max_length=50, blank=True)
def __str__(self):
return self.officer.first_name + ' '+ self.officer.last_name+ ', ' + self.incident.case_number
class Meta():
verbose_name_plural = "details"
ordering = ['incident__case_number']
class SiteText(models.Model):
content1 = tinymce_models.HTMLField()
content2 = models.TextField(max_length=500, blank=True)
OK, I spent all day debugging yesterday and got everything working properly. Here's my admin.py now, models.py is unchanged. I forget exactly which change solved the original error I posted about.
from django.contrib import admin
from import_export import resources, widgets, fields
from import_export.admin import ImportExportModelAdmin, ImportExportActionModelAdmin
from forms import AdminTextForm, OfficerTextForm
from .models import Officer, Incident, Details, SiteText
class DetailsInlineAdmin (admin.TabularInline):
model = Details
extra = 5
class OfficerResource(resources.ModelResource):
class Meta:
model = Officer
class OfficerAdmin(ImportExportModelAdmin):
list_display = ('first_name', 'last_name', 'badge', 'department')
search_fields = ['first_name', 'last_name']
inlines = [DetailsInlineAdmin]
resource_class = OfficerResource
form=OfficerTextForm
class Media:
js = ('//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js','/static/admin/js/admin/popup.js')
class IncidentResource(resources.ModelResource):
class Meta:
fields = ('officer','case_number', 'office')
model = Incident
import_id_fields = ['case_number']
class IncidentAdmin(ImportExportModelAdmin):
list_display = ('office','case_number')
search_fields = ['case_number']
inlines = [DetailsInlineAdmin]
resource_class = IncidentResource
class DetailsResource(resources.ModelResource):
class BadgeForeignKeyWidget(widgets.ForeignKeyWidget):
def get_queryset(self, value, row):
return self.model.objects.filter(
badge__iexact=row["badge"]
)
officer = fields.Field(
column_name='officer',
attribute='officer',
widget=BadgeForeignKeyWidget(Officer, 'last_name'))
incident = fields.Field(
column_name='incident',
attribute='incident',
widget=widgets.ForeignKeyWidget(Incident, 'case_number'))
class Meta:
fields = ('id','officer','incident', 'allegation', 'finding', 'action')
model = Details
class DetailsAdmin(ImportExportModelAdmin):
list_display=('incident','officer', 'allegation', 'finding', 'action')
search_fields = ['officer__last_name', 'incident__case_number']
resource_class = DetailsResource
class SiteTextAdmin(admin.ModelAdmin):
form=AdminTextForm
admin.site.register(Officer, OfficerAdmin)
admin.site.register(Incident, IncidentAdmin)
admin.site.register(Details, DetailsAdmin)
admin.site.register(SiteText, SiteTextAdmin)
Another note is that you need to put an 'id' column in your spreadsheet. I was able to leave mine blank and then Django would generate an id to use as a primary key.

Categories

Resources