I have below models.py and admin.py files in Django. I wanted to do 2 things
Merge specs field of Environment and ItemObject and store into specs of Environment which I managed to do because I was able to figure out where to place the logic. (class AddEnvironmentDetailsInlineForm(forms.ModelForm))
I need to do the same for Estimate. I need to fetch the Environment.specs and Estimate.specs, merge the two and save in Estimate.specs
The merge is like ItemObject -> Environment -> Estimate
The challenge is that I cannot figure out where to put that logic for Estimate and Environment. Do I need to create a ModelForm for Estimate to achieve a merge? I am not clear on the logic here for the files (I am still learning the concepts in Django). If anyone could make me understand, it would be great.
models.py
class Estimate(CommonModel):
...
class Environment(CommonModel):
estimate = models.ForeignKey(Estimate,related_name='environments')
logic = PythonCodeField(blank=True, null=True)
...
class Item(CommonModel):
...
class ItemTemplate(Item):
pass
class ItemObject(Item):
pass
class EnvironmentDetail(models.Model):
environment = models.ForeignKey(Environment)
item_name = models.CharField(max_length=200,blank=True)
qty = models.FloatField('Qty')
...
admin.py
class CommonAdmin(admin.ModelAdmin):
...
class EnvironmentInlineAdmin (admin.TabularInline):
model = Environment
...
formfield_overrides = {
JSONField:{ 'widget':JSONEditor },
}
...
def save_model(self, request, obj, *kwargs):
if request.user.is_superuser or request.user==obj.author:
obj.author.id=request.user.id
super(EnvironmentInlineAdmin,self).save_model(request, obj, *kwargs)
else:
raise ValidationError("author must be you")
class EstimateAdmin (CommonAdmin):
inlines = [ EnvironmentInlineAdmin ]
list_display = ('title', 'gp_code', 'otc_price','annual_price')
admin.site.register(Estimate,EstimateAdmin)
class EnvironmentDetailsInlineForm(forms.ModelForm):
class Meta:
model = EnvironmentDetail
fields = ['item_name','qty']
show_change_link = True
class AddEnvironmentDetailsInlineForm(forms.ModelForm):
class Meta:
model = EnvironmentDetail
fields = ['item_name','item', 'qty']
item = forms.ModelChoiceField(queryset=ItemTemplate.objects.all())
def clean(self):
instance = self.cleaned_data['item']
item_specs = self.cleaned_data['item'].specs
environment_specs = self.cleaned_data['environment'].specs
environment_specs.update(item_specs)
fields = [f.name for f in Item._meta.fields]
values = dict( [(x, getattr(instance, x)) for x in fields] )
new_instance = ItemObject(**values)
new_instance.save() #save new one
#instance.delete() # remove the old one
self.cleaned_data['item'] = new_instance
return self.cleaned_data # Return self.cleaned_data at the end of clean()
def save_model(self, request, obj, *kwargs):
if request.user.is_superuser or request.user==obj.author:
super(AddEnvironmentDetailsInlineForm,self).save_model(request, obj, *kwargs)
else:
raise ValidationError("author must be you")
class EnvironmentDetailsInlineAdmin (admin.TabularInline):
model = EnvironmentDetail
...
form = EnvironmentDetailsInlineForm
formfield_overrides = {
JSONField:{ 'widget':JSONEditor },
}
def change_link(self, obj):
return mark_safe('Edit Item' % \
reverse('admin:estimate_itemobject_change',
args=(obj.item.id,)))
def item_type(self, obj):
return obj.item.item_type
...
class AddEnvironmentDetailsInlineAdmin (admin.TabularInline):
model = EnvironmentDetail
fields = ('item_name','item', 'qty', )
form = AddEnvironmentDetailsInlineForm
def has_change_permission(self, request, obj=None):
return False
def item(self,obj):
return ItemTemplate
class EnvironmentAdmin (CommonAdmin):
model=Environment
save_as = True
inlines = [ EnvironmentDetailsInlineAdmin,AddEnvironmentDetailsInlineAdmin]
...
def get_model_perms(self, request):
"""
Return empty perms dict thus hiding the model from admin index.
"""
return {}
admin.site.register(Environment,EnvironmentAdmin)
I tried below but now this(dict_merge) needs to be saved to Estimate
#receiver(post_save, sender=Estimate)
def update_specs_estimate(sender, instance, **kwargs):
print("instance", instance.specs)
return instance.specs
#receiver(post_save, sender=Environment)
def take_specs_environment(sender, instance, **kwargs):
print("instance", instance.specs)
return instance.specs
def dict_merge(update_specs_estimate, take_specs_environment):
return (update_specs_estimate.update(take_specs_environment))
Related
I have 2 models in my models.py. They are Doctor and Specialty:
class Doctor(models.Model):
name = CharField(max_length=255)
specialties = ManyToManyField('Specialty', related_name='doctors')
mc_id = CharField(max_length=255, unique=True)
class Specialty(models.Model):
name = CharField(max_length=255)
mc_id = CharField(max_length=255, unique=True)
def __str__(self):
return self.name
When i'm creating new Doctor instance via admin interface (change_form.html template) I see my Doctor model fields, which I should fill with data. Among these fields there is a field specialties ("Cпециальности"), which is m2m field and contains names (as I set __str__(self) method in Specialty model to return self.name) for specialties which were previously added to Specialty model (see a screen).
All I want is to see in this list not only specialty names, but also values of mc_id field of Specialty model, but it is not my case to make it at the expense of something like this:
class Specialty(models.Model):
name = CharField(max_length=255)
mc_id = CharField(max_length=255, unique=True)
def __str__(self):
return self.name + '(' + self.mc_id + ')'
I thought i could make it through overriding of formfield_for_manytomany() method in my admin model (associated with Doctor model) in admin.py:
#admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "specialties":
queryset = Specialty.objects.all()
for specialty in queryset:
specialty.name += '(' + {specialty.mc_id} + ')'
return super().formfield_for_manytomany(db_field, request, **kwargs)
It didn't work. I'd like to know how can i solve my problem. Thank you in advance.
UPDATE: I'd also like to make a content of specialties field in the form user-dependent:
if superuser tries to add new doctor he will see in the specialties field of the form values like:
Internal Medicine (mc1_id)
Neurology (mc1_id)
Cardiology (mc2_id)
if normal user which name is 'mc1_id' tries to add new doctor he will see in the form values filtered by his name and without '(mc_id)'-part. Like:
Internal Medicine
Neurology
I think that overwrite the label_from_instance method in the form field can work for you.
class SpecialtyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
obj.name + '(' + obj.mc_id + ')'
class DoctorModelForm(forms.ModelForm):
specialties = SpecialtyModelMultipleChoiceField(
Specialty.objecs.all
# ....
)
class Meta:
model = Doctor
fields = '__all__'
#admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
form = DoctorModelForm
# ....
UPDATE:
In order to make the field text user dependent you can return different classes in the get_form method of the Admin Model:
class SpecialtyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
obj.name + '(' + obj.mc_id + ')'
class DoctorModelForm(forms.ModelForm):
specialties = SpecialtyModelMultipleChoiceField(
Specialty.objecs.all
# ....
)
class Meta:
model = Doctor
fields = '__all__'
class SimpleDoctorModelForm(forms.ModelForm):
class Meta:
model = Doctor
fields = '__all__'
#admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kw):
if request.user.is_superuser:
return DoctorModelForm
return SimpleDoctorModelForm
If you need more, you can set properties in the form and work with its in the constructor:
class DoctorModelForm(forms.ModelForm):
class Meta:
model = Doctor
fields = '__all__'
#classmethod
def set_user(cls, user):
cls.__user = user
def __init__(self, *args, **kw):
super(DoctorModelForm, self).__init__(*args, **kw)
if self.__user.condition:
self.fields['specialties'] = MyCustomField
#admin.register(Doctor)
class DoctorAdmin(admin.ModelAdmin):
form = DoctorModelForm
def get_form(self, request, obj=None, **kw):
form_class = super(DoctorAdmin, self).get_form(request, obj, **kw)
form_class.set_user(request.user)
return form_class
Just use format() in models
def __str__(self):
return '{} {}'.format(self.name, self.mc_id)
I'd like to share an altnernative realisation of Tonio's great idea.
# fields.py
class SpecialtyModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def label_from_instance(self, obj):
obj.name + '(' + obj.mc_id + ')'
# admin.py
class BaseAdmin(admin.ModelAdmin):
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# some logic
return form
#admin.register(Doctor)
class DoctorAdmin(BaseAdmin):
def formfield_for_manytomany(self, db_field, request, **kwargs):
if db_field.name == "specialties":
queryset = Specialty.objects.all().filter(mc_id=request.user)
kwargs.update({'queryset': queryset})
return super().formfield_for_manytomany(db_field, request, **kwargs)
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if request.user.is_superuser:
form.base_fields['specialties'] = SpecialtyModelMultipleChoiceField(Specialty.objects.all())
return form
I think it can be useful in situations when DoctorAdmin is inherited from some base model like BaseAdmin and this base model also has overriden get_form() method. But here in case of superuser's request queryset is formed twice, therefore Tonio's answer is definetely better.
I have a ModelForm for a parent model and and an InlineForm for a foreign key related model. The Inline form data is read only and can only be updated via a file upload. I have this working on the intial creation and save, but if you replace the students by uploading a new file, while the data updates properly after the upload and save, the inline form doesn't reload properly right after this save and displays an error. However, if you navigate away and return everything looks good.
Models.py
class Room(models.Model):
room_id = models.AutoField(primary_key=True)
name = models.TextField(blank=False, verbose_name = "Room Name ")
code = models.TextField(blank=False)
file = models.TextField(blank=False)
class Meta(object):
managed = False
db_table = 'public\".\"room'
class Student(models.Model):
room = models.ForeignKey(Room, related_name="students, to_field="room_id", db_column="room_id")
student_id = models.TextField(blank=False, primary_key=False)
#property
def grade(self):
return util.get_grade(self.student_id)
class Meta(object):
managed = False
db_table = 'public\".\"student’
admin.py
class StudentsInline(admin.TabularInline):
model = Student
form = StudentInlineForm
formset = StudentFormSet
readonly_fields = ('student_id', 'grade')
extra = 0
def get_actions(self, request):
'''
Removes the delete action
'''
actions = super(StudentsInline, self).get_actions(request)
del actions['delete_selected']
return actions
def has_delete_permission(self, request, obj=None):
return False
def has_add_permission(self, request, obj=None):
return False
class RoomAdmin(admin.ModelAdmin):
def student_count(self, obj):
return obj.students.count()
student_count.short_description = " Total Enrolled "
form = RoomForm
list_display = [name', ‘code']
readonly_fields = ['student_count']
list_per_page = 25
inlines = [StudentsInline]
def get_actions(self, request):
actions = super(RoomAdmin, self).get_actions(request)
del actions['delete_selected']
return actions
def has_delete_permission(self, request, obj=None):
return False
def save_formset(self, request, form, formset, change):
form.instance.file = 'processed file'
form.instance.save
formset.save()
forms_admin.py
class RoomsForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(RoomsForm, self).__init__(*args, **kwargs)
self.fields[‘name'].required = True
self.fields[‘code'].required = True
self.fields['file'] = forms.FileField(
label='Upload your enrollees:'
def load_file(self, upload_file):
student_list = []
data = upload_file.read() #simplified for this example
for s in reader:
s_id = s[0]
new_student = Student(student_id=s_id)
student_list.append(new_student)
return student_list
def save(self, *args, **kwargs):
room_form = super(RoomsForm, self).save(commit=False)
enrollee_list = self.load_file(self.instance.file.file)
if instance.room_id is None:
instance.file = 'uploaded'
instance = super(RoomsForm, self).save(commit=True)
Student.objects.filter(room_id=self.instance.room_id).all().delete()
instance.students.add(*enrollee_list)
return instance
class Meta:
model = Room
fields = '__all__'
class StudentForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(StudentForm, self).__init__(*args, **kwargs)
I am using ModelForm to allow multiple rows edit at the same time. It is a very simple form that has series of yes_no columns. The model looks like:
models.py
class Yn(models.Model):
yn_id = models.IntegerField(primary_key=True)
description = models.CharField(max_length=30)
def __str__(self):
return ' '.join([
self.description,
])
class Meta:
managed = False
db_table = 'yn'
class Invoice(models.Model):
description = models.CharField(max_length=50)
invoice_date = models.DateTimeField()
...
invoice_sent_yn = models.ForeignKey('Yn', models.DO_NOTHING, db_column='invoice_sent_yn', related_name="invoice_sent_yn")
confirm_receipt_yn = models.ForeignKey('Yn', models.DO_NOTHING, db_column='confirm_receipt_yn', related_name="confirm_receipt_yn")
paid_yn = models.ForeignKey('Yn', models.DO_NOTHING, db_column='paid_yn', related_name="paid_yn")
forms.py
class InvoiceGridEdit(ModelForm):
model = Invoice
fields = ['description','invoice_date','invoice_sent_yn', 'confirm_receipt_yn', 'paid_yn']
def __init__(self, *args, **kwargs):
super(JurisGridEditForm, self).__init__(*args, **kwargs)
...
...
InvoiceFormSet = modelformset_factory(models.Invoice, form=InvoiceGridEdit)
views.py
class InvoiceUpdateGrid(CreateView):
template_name = "/invoice_update_grid.html"
model = Invoice
form_class = InvoviceViewEditForm
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return redirect("account_login")
def get(self, request, *args, *kwargs):
self.object = None
customer_number = self.request.GET.get('customer_number')
invoice_form = InvoiceFormSet(queryset=Invoice.objects.filter(customer_number = customer_number)
return self.render_to_response(self.get_context_data(invoice_form=invoice_form))
def post(self, request, *args, **kwargs):
self.object = None
invoice_form = InvoiceFormSet(self.request.POST)
if (invoice_form.is_valid()):
return self.form_valid(invoice_form)
else:
return self.form_invalid(invoice_form)
def form_valid(self, invoice_form):
...
...
invoice_form.save()
return redirect("customer_list")
def form_invalid(self, invoice_form):
return self.render_to_response(self.get_context_data(invoice_form=invoice_form))
The forms works fine, get and post works, except, it takes a while (~30 sec) to retrieve and update. Using django-debug-toolbar, it looks like the yn columns retrieve separately for each column for each row (~2k rows). The Yn table only has 3 rows -1 - Unknown, 0 - No, 1 - Yes.
I tried to search for work around to stop the craziness of Django hitting the DB 900 times per retrieval. I found something about caching but I have no idea how to do it.
Thanks in advance.
I am trying to incorporate inline add to my django form. The User can create a 'Site', within the 'Site' form the user can create multible 'Staff' to that 'Site'.
I have followed a tutorial which I believe to be the solution but can not get it to work.
Currently I am getting the error:
'Calling modelformset_factory without defining 'fields' or 'exclude' explicitly is prohibited.'
Here is my attempt.
models.py
class Site(models.Model):
...
class Staff(models.Model):
site = models.ForeignKey(Site)
....
views.py
class BaseNestedFormset(BaseInlineFormSet):
def add_fields(self, form, index):
# allow the super class to create the fields as usual
super(BaseNestedFormset, self).add_fields(form, index)
form.nested = self.nested_formset_class(
instance=form.instance,
data=form.data if self.is_bound else None,
prefix=' %s-%s' % (
form.prefix,
self.nested_formset_class.get_default_prefix(),
),
)
def is_valid(self):
result = super(BaseNestedFormset, self).is_valid()
if self.is_bound:
# look at any nested formsets, as well
for form in self.forms:
result = result and form.nested.is_valid()
return result
def save(self, commit=True):
result = super(BaseNestedFormset, self).save(commit=commit)
for form in self:
form.nested.save(commit=commit)
return result
def nested_formset_factory(site_model, staff_model):
parent_child = inlineformset_factory(
site_model,
staff_model,
formset=BaseNestedFormset,
)
parent_child.nested_formset_class = inlineformset_factory(
staff_model,
)
return parent_child
class SiteCreate(CreateView):
model = Site
form_class = SiteForm
queryset = Site.objects.all()
success_url = '/site/list'
def get_form_class(self):
return nested_formset_factory(
Site,
Staff,
)
forms.py
class SiteForm(forms.ModelForm):
class Meta:
model = Site
exclude = ('creation', 'last_modified')
def nested_formset_factory(site_model, staff_model):
parent_child = inlineformset_factory(
site_model,
staff_model,
formset=BaseNestedFormset,
fields = ('one', 'two', 'ect')
)
I have two step of my WizardView process based ModelForm. I don't understand why, when I valide the second step django says me that a previous field are required. I try to send data between step like that:
forms.py
class RequestForm1(forms.ModelForm):
class Meta:
model = Product
fields = ('title', 'product_class', )
class RequestForm2(forms.ModelForm):
class Meta:
model = Product
fields = ( 'dimension_x', )
views.py
class RequestView(SessionWizardView):
instance = None
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_form_initial(self, step):
current_step = self.storage.current_step
if current_step == 'step2':
prev_data = self.storage.get_step_data('step1')
print(prev_data)
title = prev_data.get('step1-title', '')
product_class = prev_data.get('step1-product_class', '')
return self.initial_dict.get(step, {
'title': title,
'product_class': product_class
})
return self.initial_dict.get(step, {})
def done( self, form_list, **kwargs ):
self.instance.save()
return HttpResponseRedirect('/catalogue/request/')
Please find below my solution
forms.py
class RequestForm1(forms.ModelForm):
class Meta:
model = Product
fields = ('title', 'product_class', )
class RequestForm2(forms.ModelForm):
class Meta:
model = Product
fields = ( 'title', 'product_class', 'dimension_x', )
widgets = {
'title': forms.HiddenInput(),
'product_class': forms.HiddenInput()
}
views.py
class RequestView(SessionWizardView):
instance = None
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def get_form_instance( self, step ):
if self.instance is None:
self.instance = Product()
return self.instance
def get_form(self, step=None, data=None, files=None):
form = super(RequestView, self).get_form(step, data, files)
if step == 'step2':
prev_data = self.storage.get_step_data('step1')
title = prev_data.get('step1-title', '')
product_class = prev_data.get('step1-product_class', '')
form.fields['title'].initial = title
form.fields['product_class'].initial = product_class
return form
def done(self, form_list, **kwargs):
self.instance.save()
return HttpResponseRedirect('/catalogue/request/success')