I implemented a custom model field:
class CustomCharField(models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR"
Now I want to be able to display that additional attribute when I'm iterating over all model's fields in change_form_template:
{% block field_sets %}
{% for fieldset in adminform %}
{% for line in fieldset %}
{% for field in line %}
{{ field.field }}
{% if field.field.my_attr %}
{{ field.field.my_attr }}
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% endblock %}
Unfortunately my_attr is not visible. I was searching the proper method in the django docs but without any success. I was also trying to use the custom form_class (inheriting from django.forms.fields.CharField)
EDIT:
Ok, so I need both custom form field and model field. Given the following code, what should I change to make it working? (of course I ignore passing the attribute value in that example). Why my_attr from CustomFormField is still not accessible from the template?
class CustomFormField(fields.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR2"
class CustomCharField(models.CharField):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = "MY_ATTR"
def formfield(self, **kwargs):
defaults = {'form_class': CustomFormField}
defaults.update(kwargs)
return super().formfield(**defaults)
Ok, thanks to the advices from #AbdulAzizBarkat and #Guillaume I finally managed how to resolve the problem.
In order to expose attribute of the model field at the HTML template, it's needed to implement also custom form field and pass the attribute in default dict in formfield method:
from django.db import models
from django.forms import fields
class CustomFormField(fields.CharField):
def __init__(self, my_attr, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = my_attr
class CustomCharField(models.CharField):
def __init__(self, my_attr, *args, **kwargs):
super().__init__(*args, **kwargs)
self.my_attr = my_attr
def formfield(self, **kwargs):
defaults = {'form_class': CustomFormField, "my_attr": self.my_attr}
defaults.update(kwargs)
return super().formfield(**defaults)
Another change is the way of accessing to the attribute in the HTML template specified by setting change_form_template value. In that case it should be {{ field.field.field.my_attr }}
Related
Hello, I am writing a small project about a car shop and this is the problem I came up with.
I'm trying to add a new car and everything seems to work, but when I fill out the form and click submit, it just redirects me to products page without errors and without adding a new car to the database.
Here is the code.
views.py
class AddProductView(View):
action = 'Add'
template_name = 'myApp/manipulate_product.html'
context = {
}
form_class = ManipulateProductForm
def get(self, req, *args, **kwargs):
form = self.form_class()
self.context['action'] = self.action
self.context['form'] = form
return render(req, self.template_name, self.context)
def post(self, req, *args, **kwargs):
form = self.form_class(req.POST or None)
if form.is_valid():
form.save()
else:
print(form.errors)
return redirect('products', permanent=True)
models.py
class Car(models.Model):
name = models.CharField(max_length=32)
model = models.CharField(max_length=32, unique=True)
price = models.IntegerField(validators=[
MinValueValidator(0),
])
def __str__(self):
return f'{self.name} {self.model}'
forms.py
class ManipulateProductForm(forms.ModelForm):
def __init__(self, action="Submit", *args, **kwargs):
super().__init__(*args, **kwargs)
self.action = action
self.helper = FormHelper(self)
self.helper.add_input(Submit('submit', self.action, css_class='btn btn-primary'))
class Meta:
model = Car
fields = '__all__'
manipulate_product.html
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="product-manipulate-container">
{% crispy form form.helper%}
</div>
{% endblock %}
I'm sure the problem is in Crispy, because if I replace code in forms.py and manipulate_product.html to this
forms.py
class ManipulateProductForm(forms.ModelForm):
class Meta:
model = Car
fields = '__all__'
manipulate_product.html
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block content %}
<div class="product-manipulate-container">
<form action="" method="POST">
{% csrf_token %}
{{ form.as_div }}
<input type="submit" value="Submit">
</form>
</div>
{% endblock %}
Everything is working fine!
I noticed that when I use Crispy in AddProductView post method
is_valid() method returns False but without Crispy it returns True
I have tried everything except one delete the whole project and start over.
I searched on youtube , google , stackoverflow but didn't find anything similar.
Looked at the Crysp documentation, but it's also empty.
I hope someone has come across this problem and can help me.
Thank you!
Try rewriting your form like this:
class ManipulateProductForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ManipulateProductForm, self).__init__(*args, **kwargs)
self.helper = FormHelper(self)
self.helper.form_action = 'Submit'
self.helper.add_input(Submit('submit', 'Submit', css_class='btn btn-primary'))
class Meta:
model = Car
fields = '__all__'
And in your template you can just do the following, since you used the default name of the helper:
{% crispy form %}
I'm able to insert (lame) static text onto the change form admin page, but I'd really like it to use the context of the current object being edited!
For instance, I want to format on the MyObject change form a URL to include the ID from a ForeignKey connected object (obj) as a link.
My admin objects:
class MyObjectChangeForm(forms.ModelForm):
class Meta:
model = MyObject
fields = ('field1', 'obj',)
class MyObjectAdmin(admin.ModelAdmin):
form = MyObjectChangeForm
list_display = ('field1', 'obj')
def render_change_form(self, request, context, *args, **kwargs):
self.change_form_template = 'admin/my_change_form.html'
extra = {'lame_static_text': "something static",}
context.update(extra)
return super(MyObjectAdmin, self).render_change_form(request,
context, *args, **kwargs)
My template templates/admin/my_change_form.html:
{% extends "admin/change_form.html" %}
{% block form_top %}
{{ lame_static_text }}
<a href="http://example.com/abc/{{ adminform.data.obj.id }}?"/>View Website</a>
{% endblock %}
The {{adminform.data.obj.id}} call obviously doesn't work, but I'd like something along those lines.
How do I insert dynamic context from the current object into the admin change form?
Add your extra context in change_view
class MyObjectAdmin(admin.ModelAdmin):
# A template for a very customized change view:
change_form_template = 'admin/my_change_form.html'
def get_dynamic_info(self):
# ...
pass
def change_view(self, request, object_id, form_url='', extra_context=None):
extra_context = extra_context or {}
extra_context['osm_data'] = self.get_dynamic_info()
return super(MyObjectAdmin, self).change_view(
request, object_id, form_url, extra_context=extra_context,
)
I believe the magic variable you seek is 'original', this contains the python object the change form is editing:
<a href="http://example.com/abc/{{ original.id }}?"/>View Website</a>
I have 3 inline models in a Parent modelAdmin. I wish to display the value of a field 'contact' present in 1 of the inline models in the Parent ModelAdmin. In change_form.html of the django admin:
{% block inline_field_sets %}
{% for inline_admin_formset in inline_admin_formsets %}
{% ifequal inline_admin_formset.formset.prefix 'client_executive' %}
{{ inline_admin_formset.formset.form.contact }}
{% endifequal%}
{% include inline_admin_formset.opts.template %}
{% endfor %}
{% endblock %}
This does not show the value of the populated 'contact' value in the form in the template. The inline model has attribute extra=2. It displays nothing. What is the mistake? How can I access this value easily? Using django 1.6.5
yes you can do it. Easy than you think:
i wrote article about it
inline in middle of change form
in short terms:
add read only field "contact" in change form.
get inline, which you need, on render this new field "contact".
return render of this inline, don't forget to set only those fields in inline, which you want
Profit.
you need to add around 6 lines of code in you project
admin.register(Product)
class ProductModelAdmin(admin.ModelAdmin):
inlines = (ImageAdminInline,)
fields = ('title', 'image_inline', 'price')
readonly_fields= ('image_inline',) # we set the method as readonly field
def image_inline(self, obj=None, *args, **kwargs):
context = obj.response['context_data']
inline = context['inline_admin_formset'] = context['inline_admin_formsets'].pop(0)
return get_template(inline.opts.template).render(context, obj.request)
def render_change_form(self, request, context, *args, **kwargs):
instance = context['adminform'].form.instance # get the model instance from modelform
instance.request = request
instance.response = super().render_change_form(request, context, *args, **kwargs)
return instance.response
I think you have a typo, in Django source I found this: {% for inline_admin_form in inline_admin_formset %} you have an additional 's' {% for inline_admin_formset in inline_admin_formsets %}
I'll be implementing a customized class based generic view by sub-classing ListView in my views.py. My question is how will be able to access the request (HttpRequest object) parameter in my sub-class? The HttpRequest object that I am pertaining to is the default request parameter for all functions inside views.py. Example:
def search(request):
To be clearer, here's what I've tried so far:
**views.py
class CustomListView(ListView):
temp = ""
def get(self, request, *args, **kwargs):
self.temp = request.GET.get('temp')
return super(CustomListView, self).get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(CustomListView, self).get_context_data(**kwargs)
context['temp'] = self.temp
return context
**urls.py
url(r'^temp/$, CustomListView.as_view(queryset=Document.objects.all()[:1],template_name="temp.html")),
**temp.html
{% extends 'base.html' %}
{% block content %}
<h2>{{ temp }}
{% endblock %}
But all I am seeing when I run the server and access /temp/ (temp.html) is 'None'. So meaning, 'temp' is "" or 'temp' was not created at all.
Any ideas are greatly appreciated. Thanks!
In general, you can use self.request in CBV methods that haven't been passed a request.
So you can use
context['temp'] = self.request.GET.get('temp')
in your get_context_data method, and then delete your get override method entirely.
I have the following function in one of my models
def get_fields(self):
return[(field.name, field.value_to_string(self)) for field in MusicPack._meta.fields]
Which helps me iterate over all the fields of a model and display them into a template. How would I implement this to all my models without replicating the two lines in every model in my database?
Would I just make a superclass that contain the function for all models and then have all my models children of it?
template.html
<div id = "subtemplate">
<ul>
{% for model in object_list %}
<div class = modeldiv>
{% for name,value in model.get_fields %}
<li>
{% if value %}
{{ name|capfirst }} : {{ value }}
{% endif %}
</li>
{% endfor %}
</div>
{% empty %}
<li> No objects created yet. </li>
{% endfor %}
</ul>
</div>
You could use Mixins.
Example
class Mixin(object):
def get_fields(self):
return[(field.name, field.value_to_string(self)) for field in self.__class__._meta.fields]
class A(models.Model, Mixin):
...
class B(models.Model, Mixin):
...
Putting that method in a base class is certainly one way to do it. Another approach is to have it be a generic utility function in some utility module that prints all fields in an object, which you can call before rendering your template.
from myproject.utilities import get_fields
...
return render_template("template.html", get_fields(model))
Yet a third approach is to write a class decorator which provides this functionality to a class:
class with_get_fields(cls):
def __init__(self, *args, **kwargs):
cls.__init__(self, *args, **kwargs)
def get_fields():
return [(field.name, field.value_to_string(self)) for field in cls.fields]
and then apply it to any model class that you wish to have this functionality
#with_get_fields
class model():
def __init___(self):
...