i have field('image_tag') from Inlinemodel that i want to display in one row of Orderdetail model.
class SampleImagesInline(admin.StackedInline):
fields = ['image_tag']
readonly_fields = ['image_tag']
model = SampleImages
extra = 0
#admin.register(OrderDetail)
class OrderDetailAdmin(admin.ModelAdmin):
inlines = [SampleImagesInline]
by default these are showing vertically. how to display in one row?.
You can use TabularInline. Try like this:
class SampleImagesInline(admin.TabularInline):
fields = ['image_tag']
readonly_fields = ['image_tag']
model = SampleImages
extra = 0
Update
I think I misunderstood your problem. IMHO, you should not use the InLineAdmin. Instead, try like this:
from django.utils.safestring import mark_safe
...
class OrderDetailAdmin(admin.ModelAdmin):
...
readonly_fields = ['image_tags',]
def image_tags(self, obj):
img_html = ""
for image in obj.image_set.all(): # <-- get related images
img_html += "<img src={}> ".format(image.image.url)
same_line_html = '<div class="tabular inline-related last-related">{}</div>'.format(img_html)
return mark_safe(same_line_html)
image_tags.description = "Images"
Please see here in docs for more information on getting related objects
Related
I'd like to retrieve a model's objects via a search form but add another column for search score. I'm unsure how to achieve this using django-tables2 and django-filter.
In the future, I'd like the user to be able to use django-filter to help filter the search result. I can access the form variables from PeopleSearchListView but perhaps it's a better approach to integrate a django form for form handling?
My thought so far is to handle to the get request in get_queryset() and then modify the queryset before it's sent to PeopleTable, but adding another column to the queryset does not seem like a standard approach.
tables.py
class PeopleTable(tables.Table):
score = tables.Column()
class Meta:
model = People
template_name = 'app/bootstrap4.html'
exclude = ('id',)
sequence = ('score', '...')
views.py
class PeopleFilter(django_filters.FilterSet):
class Meta:
model = People
exclude = ('id',)
class PeopleSearchListView(SingleTableMixin, FilterView):
table_class = PeopleTable
model = People
template_name = 'app/people.html'
filterset_class = PeopleFilter
def get_queryset(self):
p = self.request.GET.get('check_this')
qs = People.objects.all()
####
# Run code to score users against "check_this".
# The scoring code I'm using is complex, so below is a simpler
# example.
# Modify queryset using output of scoring code?
####
for person in qs:
if person.first_name == 'Phil' and q == 'Hey!':
score = 1
else:
score = 0
return qs
urls.py
urlpatterns = [
...
path('search/', PeopleSearchListView.as_view(), name='search_test'),
... ]
models.py
class People(models.model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
Edit:
The scoring algorithm is a bit more complex than the above example. It requires a full pass over all of the rows in the People table to generate a score matrix, before finally comparing each scored row with the search query. It's not a one-off score. For example:
def get_queryset(self):
all = []
for person in qs:
all.append(person.name)
# Do something complex with all,
# e.g., measure cosine distance between every person,
# and finally compare to the get request
scores = measure_cosine(all, self.request.GET.get('check_this'))
# We now have the scores for each person.
So you can add extra columns when you initialise the table.
I've got a couple of tables which do this based on events in the system;
def __init__(self, *args, **kwargs):
"""
Override the init method in order to add dynamic columns as
we need to declare one column per existent event on the system.
"""
extra_columns = []
events = Event.objects.filter(
enabled=True,
).values(
'pk', 'title', 'city'
)
for event in events:
extra_columns.append((
event['city'],
MyColumn(event_pk=event['pk'])
))
if extra_columns:
kwargs.update({
'extra_columns': extra_columns
})
super().__init__(*args, **kwargs)
So you could add your score column similar to this when a score has been provided. Perhaps passing your scores into the table from the view so you can identify they're present and add the column, then use the data when rendering the column.
extra_columns doesn't appear to be in the tables2 docs, but you can find the code here; https://github.com/jieter/django-tables2/blob/master/django_tables2/tables.py#L251
When you define a new column for django-tables2 which is not included in table data or queryset, you should provide a render method to calculate it's value.
You don't have to override get_queryset if a complex filtering, preprocess or join required.
In your table class:
class PeopleTable(tables.Table):
score = tables.Column(accessor="first_name")
class Meta:
model = People
def render_score(self, record):
return 1 if record["first_name"] == "Phil" and q == "Hey!" else 0
In your view you can override and provide complex data as well as special filtering or aggregates with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["filter"] = self.filter
aggs = {
"score": Function("..."),
"other": Sum("..."),
}
_data = (
People.objects.filter(**params)
.values(*values)
.annotate(**aggs)
.order_by(*values)
.distinct()
)
df = pandas.DataFrame(_data)
df = df....
chart_data = df.to_json()
data = df.to_dict()...
self.table = PeopleTable(data)
context["table"] = self.table
context['chart_data']=chart_data
return context
So I want to create a field in my Django models.py, so that the user can select a number of years as integer (e.g. 3) and then after each new entry the words 'years' to be automatically displayed. I do not want to do this with CharField.
class StudyProgramme(models.Model):
period = models.IntegerField(2)
You can write a serializer for the same.
from rest_framework import serializers
class StudyProgrammeSerializer(serializers.ModelSerializer):
def get_period(self, obj):
return str(obj.period) + "years"
class Meta:
model = StudyProgramme
fields = ['period']
I don't get it yet but i think you can do it like:
# Check the model StudyProgramme
If StudyProgramme.Objects.all().count() > 0: //Show
Else: //request to put it it
# And for sure, return it to template
When I inherit from admin.ModelAdmin, in history on admin page I can see what fields has been changed. However, now I need to use django-simple-history to track all my model changes. Now, for admin, I inherit for simple_history.SimpleHistoryAdmin. Whilst I can see all of the model changes and revert them, I cannot see, which fields were changed. Is it possible to add that handy functionality to SimpleHistoryAdmin?
I found a way to solve this issue. I added a ModelAdmin method and used History Diffing to add a custom field in the Change history table.
history_list_display = ['changed_fields']
def changed_fields(self, obj):
if obj.prev_record:
delta = obj.diff_against(obj.prev_record)
return delta.changed_fields
return None
What you need is history_list_display field in your Admin. The list of fields included in the history_list_display will be displayed in the history page with their corresponding entries.
Something like this:
class SomeAdmin(admin.ModelAdmin):
def some_user_defined(self, obj):
return "something"
date_hierarchy = 'created_at'
search_fields = ['field1', 'field2']
list_display = ('field1', 'field2',)
list_filter = ('field1',)
history_list_display = ('field1', 'field2', 'some_user_defined',)
This will display field1, field2 along with comment, user and reason
You probably want to do something like that:
# admin.py
from django.contrib import admin
from simple_history.admin import SimpleHistoryAdmin
from .models import Website
from django.utils.html import format_html
class WebsiteHistoryAdmin(SimpleHistoryAdmin):
history_list_display = ["changed_fields","list_changes"]
def changed_fields(self, obj):
if obj.prev_record:
delta = obj.diff_against(obj.prev_record)
return delta.changed_fields
return None
def list_changes(self, obj):
fields = ""
if obj.prev_record:
delta = obj.diff_against(obj.prev_record)
for change in delta.changes:
fields += str("<strong>{}</strong> changed from <span style='background-color:#ffb5ad'>{}</span> to <span style='background-color:#b3f7ab'>{}</span> . <br/>".format(change.field, change.old, change.new))
return format_html(fields)
return None
admin.site.register(Website, WebsiteHistoryAdmin)
And you get this as a result:
And if you want to view not only names of changed fields as per Rafi comment and also changed values, next code will do it:
def changed_fields_with_values(self, obj):
fields = ""
if obj.prev_record:
delta = obj.diff_against(obj.prev_record)
for change in delta.changes:
fields += str("{} changed from {} to {}".format(change.field, change.old, change.new))
return fields
return None
Similar to the previous solution from Rafi but using array to list more elegantly the record changes:
def list_changes(self, obj):
diff = []
if obj.prev_record:
delta = obj.diff_against(obj.prev_record)
for change in delta.changes:
diff.append("<b>* {}:</b> changed from `{}` to `{}`".format(change.field, change.old, change.new))
return mark_safe("\n<br>".join(diff))
i have a problem. Here is 2 related models:
class Auto(models.Model):
...
class Part(models.Model):
...
parent = models.ForeignKey(Auto, blank = False, null = False)
So, i want to create next django form:
Auto1.field1 Auto1.Part1.field1 Auto1.field2
Auto1.Part1.field2
Auto1.Part2.field1
Auto1.Part2.field2
...
Auto2.field1 Auto2.Part1.field1 Auto2.field2
Auto2.Part1.field2
Auto2.Part2.field1
Auto2.Part2.field2
...
...
All fields must be updatable, as for model Auto and for model Part.
How can i do this?
You should try to use Inline formsets. For example, assuming you have an Auto modelform it can be done like this:
autoforms = []
part_formsets = []
autos = Auto.objects.all()
PartFormSet = inlineformset_factory(Auto, Part, fields=('field1', 'field2'))
for auto in autos:
autoform = AutoForm(instance=auto)
autoforms.append(autoform)
part_formset = PartFormSet(instance=auto)
part_formsets.append(part_formset)
c = {'autoforms': autoforms, 'part_formsets': part_formsets}
return render(request, 'some.html', c)
Now you can iterate autoforms and part_formsets in template to get the desired forms. However, I have not checked if it is easy to render them in a template as you want.
Due to a BD design where depending on a value, the data is stored in different cells, I have to add form fields dynamically. I was thinking on this:
class EditFlatForm(BaseModelForm):
on_sale = forms.BooleanField(required=False)
on_rent = forms.BooleanField(required=False)
class Meta:
model = Flat
fields = ('title', 'flat_category', 'description')
...
def __init__(self, *args, **kwargs):
super(EditFlatForm, self).__init__(*args,**kwargs)
flat_properties = FlatProperty.objects.all()
for p in flat_properties:
if p.type_value == 1:
# Text
setattr(self, p.title, forms.CharField(label=p.human_title, required=False))
elif p.type_value == 2:
# Number
setattr(self, p.title, forms.IntegerField(label=p.human_title, required=False))
else:
# Boolean
setattr(self, p.title, forms.BooleanField(label=p.human_title, required=False))
But the fields don't get added, what am I missing?
I recommend creating the form on the fly using type. So, you'll need to create a function that will generate a list of all the fields you want to have in your form and then use them to generate the form, something like this :
def get_form_class():
flat_properties = FlatProperty.objects.all()
form_fields = {}
for p in flat_properties:
if p.type_value == 1:
form_fields['field_{0}'.format(p.id)] = django.forms.CharField(...)
elif p.type_value == 2:
form_fields['field_{0}'.format(p.id)] = django.forms.IntegerField(...)
else:
form_fields['field_{0}'.format(p.id)] = django.forms.BooleanField(...)
# ok now form_fields has all the fields for our form
return type('DynamicForm', (django.forms.Form,), form_fields )
Now you can use get_form_class wherever you want to use your form, for instance
form_class = get_form_class()
form = form_class(request.GET)
if form.is_valid() # etc
For more info, you can check my post on creating dynamic forms with django:
http://spapas.github.io/2013/12/24/django-dynamic-forms/
Update to address OP's comment (But then how to gain advantage of all the things ModelForm provides?): You can inherit your dynamic form from ModelForm. Or, even better, you can create a class that is descendant of ModelForm and defines all the required methods and attributes (like clean, __init__, Meta etc). Then just inherit from that class by changing the type call to type('DynamicForm', (CustomForm,), form_fields ) !
Assuming that p.title is a string variable, then this should work:
if p.type_value == 1:
# Text
self.fields[p.title] = forms.CharField(label=p.human_title, required=False))