How to add different model to ModelForm as extra field? - python

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)

Related

How to auto add a field in django admin model calculating age

is it possible to add an age field that is auto filled in the runtime based on another date of birth field at the django admin interface, i added a screenshot trying to explain more what i mean
my models.py
class FamilyMember(models.Model):
transaction = models.ForeignKey(Transaction, on_delete=models.CASCADE)
family_group = models.ForeignKey(FamilyGroup,
on_delete=models.CASCADE,
null=True,
blank=True)
name = models.CharField(max_length=100, null=True, blank=True)
date_of_birth = models.DateField(null=True, blank=True)
relationship = models.ForeignKey(Relationship, on_delete=models.PROTECT)
dependant_child_age_range = models.ForeignKey(DependantChildAgeRange,
null=True,
blank=True,
on_delete=models.PROTECT)
care_percentage = models.PositiveSmallIntegerField(
null=True, blank=True, validators=[
MaxValueValidator(100),
])
income = models.DecimalField(max_digits=6,
decimal_places=2,
null=True,
blank=True)
rent_percentage = models.PositiveSmallIntegerField(
null=True, blank=True, validators=[
MaxValueValidator(100),
])
admin.py
class FamilyMemberInline(admin.TabularInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
action = request.META['PATH_INFO'].strip('/').split('/')[-1]
if action == 'change':
transaction_id = request.META['PATH_INFO'].strip('/').split('/')[-2]
if db_field.name == "family_group":
kwargs["queryset"] = FamilyGroup.objects.filter(transaction=transaction_id)
return super(FamilyMemberInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
model = FamilyMember
extra = 0
def sufficient_info_provided (self, obj):
return obj.sufficient_information_provided
sufficient_info_provided.boolean = True
readonly_fields = ['sufficient_info_provided',]
Override your inline's get_queryset method to annotate the queryset with the calculation. The annotation will add an age attribute to each object in the queryset.
Then as you can see in the ModelAdmin.list_display documentation, you can include a string representing a ModelAdmin method that accepts one argument, the model instance. Inline's work in the same way but you must include the method in ModelAdmin.readonly_fields.
Putting it all together:
class FamilyMemberInline(admin.TabularInline):
...
fields = (..., 'get_age')
readonly_fields = ('get_age',)
def get_queryset(self, request):
return (
super().get_queryset(request)
.annotate(age=...)
)
def get_age(self, instance):
return instance.age

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.

ValueError at /feedback/by/XXX/new Cannot assign "<SimpleLazyObject: <User: XXX>>": "Model.user" must be a "User" instance

I am trying to build this feedback application for the students to give feedback to their teachers. I am getting the following error when I try to create an instance of the feedback form. Please help. Thanks.
Error:
ValueError at /feedback/by/XXX/new
Cannot assign "<SimpleLazyObject: <User: XXX>>": "Feedback.user" must be a "User" instance.
CreateView:
class CreateFeedback(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
form_class = FeedbackForm
model = models.Feedback
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
Form:
class FeedbackForm(forms.ModelForm):
class Meta:
fields = ("teacher", "subject", "param1", "param2", "param3", "param4",
"param5", "param6", "param7", "param8", "param9", "param10")
model = models.Feedback
Path in urls.py:
path('by/<username>/new',views.CreateFeedback.as_view(),name='create'),
Models.py:
class Feedback(models.Model):
user = models.ForeignKey(User, related_name = 'feedback', on_delete=models.CASCADE, blank=False, null=False)
teacher = models.ForeignKey(Teacher, related_name = 'feedback', on_delete=models.CASCADE, blank=False, null=False)
subject = models.ForeignKey(Subject, related_name = 'feedback', on_delete=models.CASCADE, blank=False, null=False)
param1 = models.PositiveIntegerField(blank=False, null=False)
param2 = models.PositiveIntegerField(blank=False, null=False)
param3 = models.PositiveIntegerField(blank=False, null=False)
param4 = models.PositiveIntegerField(blank=False, null=False)
param5 = models.PositiveIntegerField(blank=False, null=False)
param6 = models.PositiveIntegerField(blank=False, null=False)
param7 = models.PositiveIntegerField(blank=False, null=False)
param8 = models.PositiveIntegerField(blank=False, null=False)
param9 = models.PositiveIntegerField(blank=False, null=False)
param10 = models.PositiveIntegerField(blank=False, null=False)
def get_absolute_url(self):
return reverse(
"feedback:detail",
kwargs={"username": self.user.username, "pk": self.pk})
def __str__(self):
return (self.username + "->" + self.teacher)
class Meta:
ordering = ["teacher"]
unique_together = ["user", "teacher", "subject"]
class User(auth.models.User, auth.models.PermissionsMixin):
def __str__(self):
return self.username
Earlier Django was allowing an integer to assign to a user object, but don't know since when, but it needs a user object only.
To fix your issue
Replace the following line
self.object.user = self.request.user
with following one
self.object.user = User.objects.get(id=self.request.user.id)

Insert related field django rest framework

I have this simple model and I am having difficult in inserting related field of 'notes' through django rest framework.
class Student(models.Model):
firstName = models.CharField(max_length=100, blank=True, null=True)
lastName = models.CharField(max_length=100, blank=True, null=True)
prefLocation = models.ManyToManyField("Location", blank=True, null=True, related_name = 'prefLocation')
def __unicode__(self):
return self.firstName
class LocationRegion(models.Model):
regionName = models.CharField(max_length=100)
def __unicode__(self):
return self.regionName
class Location(models.Model):
locationName = models.CharField(max_length=100)
region = models.ForeignKey(LocationRegion, null=True, blank=True, related_name='locations')
def __unicode__(self):
return self.locationName
class Note(models.Model):
text = models.CharField(max_length=1000)
student = models.ForeignKey(Student, null=True, blank=True, related_name='notes')
def __unicode__(self):
return self.text
class StudentViewSet(viewsets.ModelViewSet):
queryset = Student.objects.all()
serializer_class = StudentSerializer
I am unsure if I need to use ModelSerializer or generic Serializer. Validated_data doesn't return 'note' field in the deserialized data. I would appreciate any help with the serializer.
Thanks
Here are my serializers :
class StudentSerializer(serializers.ModelSerializer):
def create(self, validated_data):
def get_notes(self, obj):
return validated_data['note']
note = serializers.SerializerMethodField('get_notes')
return Candidate.objects.create(**validated_data)
class Meta:
model = Student
fields = ('id', 'firstName', 'lastName', 'note')
class NoteSerializer(serializers.ModelSerializer):
class Meta:
model = Note

Adding 2 models inside a Admin Manager

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

Categories

Resources