Custom view on admin site - python

I would like to have a custom view on a model admin.
Current behavior:
When i click on Model object(1) it takes me to site/app_label/1/change,
This leads to edit of the object with its inlines.
What I want to achieve:
when i click on a object should redirect to site/app_label/1 it should show a list view of its inlines (Not a editable list) i will put a separate button which will lead to site/app_label/1/change
I want this list view to be similar to Django default style listing.
My two models are:
class RunConfig(models.Model):
config_name = models.CharField(max_length=100,blank=False,null=False, unique=True )
class Jobs(models.Model):
config_name = models.ForeignKey('RunConfig',on_delete=models.CASCADE,)
job_name = models.TextField(blank=True,null=True, )
parameters = models.TextField(blank=True,null=True, )
exec_order = models.IntegerField(help_text="Execution Order")
admin.py
class JobsInline(SortableInlineAdminMixin, admin.TabularInline):
model = Jobs
extra = 1
class RunConfigAdmin(admin.ModelAdmin):
list_display = ('config_name',)
What I have tried till now:
My plan was to override the changelist template but that did not work as it started to complain about no reverse match found for app_list
in admin.py added custom url
def get_urls(self):
urls = super(RunConfigAdmin, self).get_urls()
my_urls = [
url(r'^(?P<pk>[0-9]+)/$', self.admin_site.admin_view(RunConfigDetailView.as_view(), cacheable=True), name='rc-detail'),
]
return my_urls + urls
created a detailed view on views.py i was expecting it wil be easier to use the context['job'] to achive the result but I failed :(
class RunConfigDetailView(LoginRequiredMixin,DetailView):
model = RunConfig
context_object_name = 'rc_detail'
def get_context_data(self,**kwargs):
context = super(RunConfigDetailView, self).get_context_data(**kwargs)
context['opts'] = self.model._meta
context['jobs'] = Jobs.objects.filter(config_name=self.object.id)
return context

Related

How can I create a view that retrieves all objects with matching primary key in url?

I have two models that form a one-to-many relationship. One pallet has multiple images associated with it.
models.py
class Pallet(models.Model):
pallet_id = models.IntegerField(primary_key=True)
def __str__(self):
return str(self.pallet_id)
class Meta:
ordering = ('pallet_id',)
class Image(models.Model):
created = models.DateTimeField(auto_now_add=True)
pallet = models.ForeignKey(Pallet, on_delete=models.CASCADE)
image = models.FileField(upload_to="")
def __str__(self):
return str(self.created)
I'm trying to create a view where I get all images associated with a particular pallet_id from the url.
serializers.py
class ImageSerializer(serializers.HyperlinkedModelSerializer):
pallet = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
class Meta:
model = Image
fields = '__all__'
class PalletSerializer(serializers.ModelSerializer):
class Meta:
model = Pallet
fields = '__all__'
urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pk>[0-9]+)/$', views.PalletDetail.as_view(), name='pallet-detail'),
])),
]
I think the issue is in the views.py with the PalletDetail class. I am confused on how to write the view based on the primary key from the URL. I've tried to use **kwargs['pk'] but does using this make it a function-based view? If so, would it be bad form to mix class-based and function-based views? How can I get similar behavior from class-based views?
I'm really struggling with the views here:
views.py
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Image.objects.prefetch_related('pallet').all()
serializer_class = ImageSerializer
In Function based view
pass pk parameter
example:
def Example_function(request, pk):
queryset = Model.objects.filter(id=pk)
In Class-Based views
get url parameter
pk = self.kwargs.get('pk')
queryset = Model.objects.filter(id=pk)
You're going about it the wrong way. You want to return a list of images belonging to the pallet so what you need is a list view for the images and not a pallet detail view. The url should look like this:
/pallets/<pallet_id>/images/
In this way the url is self-descriptive and by REST standards you can easily see that you are requesting for images belonging to a pallet and not the pallet itself
In urls.py
urlpatterns = [
url(r'^pallets/', include([
url(r'^(?P<pallet_pk>[0-9]+)/images/$', views.PalletImagesListView.as_view(), name='pallet-images-list'),
])),
]
In views, you have to overide the get_queryset() method, so that it only returns the images for the pallet specified
class PalletImagesListView(generics.ListAPIView):
serializer_class = ImageSerializer
def get_queryset(self):
return Image.objects.prefetch_related('pallet').filter(pallet__pk=self.kwargs.get('pallet_pk'))
So now you can request the first pallet's images with /pallets/1/images/
You are using the serializer and view in a wrong way.
The problem with yours PalletDetail view is that you are using the wrong queryset. The queryset should return Pallet objects, since the lookup will be on that queryset (default lookup field is 'pk'). Also, you will then need to use the serializer for Pallet since that is supposed to handle the data for the Pallet object.
class PalletDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Pallet.objects.all()
serializer_class = PalletSerializer
Have you tried filter_by() method
queryset = Model.query.filter_by(id=pk).first()

HyperlinkedRelatedField DRF does not work with a ViewSet

I have these models:
class ExamSheet (models.Model):
pass
class Exam(models.Model):
exam_sheet = models.ForeignKey('myapp.ExamSheet',
related_name='exams',
)
Serializer:
class ExamBaseSerializer(serializers.ModelSerializer):
exam_sheet = serializers.HyperlinkedRelatedField(queryset=ExamSheet.objects.all(), view_name='examsheet-detail')
class Meta:
model = Exam
fields = ('id', 'user', 'exam_sheet', )
read_only_fields = ('id', 'user',)
ViewSets:
class ExamViewSet(MultiSerializerViewSet):
queryset = Exam.objects.all()
class ExamSheetViewSet(MultiSerializerViewSet):
queryset = ExamSheet.objects.all()
Routes:
app_name = 'exams_api'
router = DefaultRouter()
router.register(r'exams', views.ExamViewSet)
router.register(r'exams_sheets', views.ExamSheetViewSet)
urlpatterns = []
urlpatterns += router.urls
Global app urls:
urlpatterns = [
path('api/', include('exams_api.urls')),
]
GenericViewSet:
class MultiSerializerViewSet(viewsets.ModelViewSet):
serializers = {
'default': None,
}
def get_serializer_class(self):
return self.serializers.get(self.action, self.serializers['default'])
But this throws me an error:
ImproperlyConfigured at /api/exams/
Could not resolve URL for hyperlinked relationship using view name
"examsheet-detail". You may have failed to include the related model
in your API, or incorrectly configured the lookup_field attribute on
this field.
How can I use HyperlinkedRelatedField to show a link to a related model in my serializer?
The view_name you set for the hyperlink field view_name='examsheet-detail, do you have a view named examsheet-detail in your urlconf(mostly urls.py) for that app? If not, after include(views.someview), add ,name='examsheet-detail' as a third parameter to path.
Have you set app_name in the specific app's urlsconf? Removing it solved the issue for me.
Make sure <int:pk> in the url path for that view and not <int:id> or something else. Else, set lookup_field='id' cos the default is pk.
Make sure that view takes in the pk parameter.

Django: DetailView and multiple slugs

I have an issue with my DetailView. I want to make sure both values are in the url string and then want to display the page. However I am always receiving this error here:
KeyError at /orders/ticket/ug2dc78agz-1/d04fkjmo37/
'order_reference'
views.py
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['order_reference'],
).filter(
access_key=self.kwargs['access_key'],
)
urls.py
urlpatterns = [
path(
'ticket/<slug:ticket_reference>/<slug:access_key>/',
TicketView.as_view(),
name='ticket'
),
]
You get the error because you are trying to access self.kwargs['order_reference'], but you don't use order_reference in the path().
Your path() uses,
'ticket/<slug:ticket_reference>/<slug:access_key>/'
therefore you can use self.kwargs['ticket_reference'] and self.kwargs['access_key'].
Since your path does not contain slug or pk, Django will not know how to fetch the object for the detail view. I would override get_object instead of get_queryset:
def get_object(self):
return get_object_or_404(
Attendee,
order__order_reference=self.kwargs['slug:ticket_reference'],
access_key=self.kwargs['access_key'],
)
You have ticket_reference url variable, but in view using order_reference. You should rename it:
class TicketView(DetailView):
model = Attendee
template_name = 'orders/ticket_view.html'
def get_queryset(self):
return Attendee.objects.filter(
order__order_reference=self.kwargs['ticket_reference'],
).filter(
access_key=self.kwargs['access_key'],
)

Allow user to select random blog bost

I've started using Django 2.0 and Python 3.6.3 to develop a website which will display customized "Posts", as in a blog Post.
I want the user to be able to click a button that basically says "Random Post". This button will take them to a template which loads a random blog post.
Here's my Post model:
class Post(models.Model):
post_id = models.AutoField(primary_key=True)
... other fields
... other fields
def publish(self):
self.published_date = timezone.now()
self.save()
def __str__(self):
return self.title
Here are some relevant views:
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
class PostDetailView(DetailView):
model = Post
template_name = 'blog/post_detail.html'
Here is my problematic view:
import random
def random_post(request):
post_ids = Post.objects.all().values_list('post_id', flat=True)
random_obj = Post.objects.get(post_id=random.choice(post_ids))
context = {'random_post': random_obj,}
return render(request, 'blog/random_post.html', context)
Here I am trying to create a Values List of all post_id values for the Post model. Then I'm trying to get a random choice from this Values List, which would be a random id. Then I'm trying to create a context and render a template using this logic.
Here are relevant urlpatterns:
urlpatterns = [
path('post/<int:pk>/',
views.PostDetailView.as_view(),name='post_detail'),
path('post/random/<int:pk>', views.random_post, name='random_post'),
Needless to say this is not working.
If I leave off the "int:pk" it renders a blank template with no data - no blog Post. If I include the , it causes an error - No Arguments Found. I assume that no data is being queried in the view, or the data isn't being properly sent from the view to the template.
I am new to Django. I appreciate your help!
For the behaviour you want, your URLs should be:
urlpatterns = [
path('post/random/', views.random_post, name='random_post'),
path('post/<int:pk>/',
views.PostDetailView.as_view(),name='post_detail'),
]
and your random_post view is:
def random_post(request):
post_count = Post.objects.all().count()
random_val = random.randint(1, post_count-1)
post_id = Post.objects.values_list('post_id', flat=True)[random_val]
return redirect('post_detail', pk=post_id)
This makes two queries - one to get the count of all posts, and one to get ID of the post in the random position. The reason for doing this is that then you don't have to get all of the IDs from the database - if you have thousands of posts, that would be very inefficient.
Here's the view that works.
def random_post(request):
post_count = Post.objects.all().count()
random_val = random.randint(0, post_count-1)
post_id = Post.objects.values_list('post_id', flat=True)[random_val]
return redirect('post_detail', pk=post_id) #Redirect to post detail view

Create Django Admin Intermediate page

I need a way to have an intermediate page shown when I´ve saved a model in django admin.
What I want to accomplish is after "saving" a model, show a page with all the attributes of the model lined up and then have a button that says Print. I used to solve this with Jquery dialog div when clicking save. That meant that I showed the settings print view before actually saving the model but I need the model to validate first now.
Its like the way that the "delete model" action is implemented. I just can´t seem to find out where to start looking though.
Edit:
I´ve started looking in the django.contrib.admin.options.py for the response_change and response_add methods. Not sure how to override them though. And its only needed for one specific model so its not generic. Also I´ve discovered the list of templates in the Class ModelAdmin. Still not sure about how to proceed without hacking the admin to bits.
Edit 2:
Added my working solution down below.
You could create a form that had an extra step of validation for the 'are you sure' step.
Given this model in our models.py:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=100)
Add a form in forms.py:
from django import forms
from .models import Person
class PersonForm(forms.ModelForm):
i_am_sure = forms.BooleanField(required=False, widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(PersonForm, self).__init__(*args, **kwargs)
if self.errors.get('i_am_sure'):
# show the 'are you sure' checkbox when we want confirmation
self.fields['i_am_sure'].widget = forms.CheckboxInput()
def clean(self):
cleaned_data = super(PersonForm, self).clean()
if not self.errors:
# only validate i_am_sure once all other validation has passed
i_am_sure = cleaned_data.get('i_am_sure')
if self.instance.id and not i_am_sure:
self._errors['i_am_sure'] = self.error_class(["Are you sure you want to change this person?"])
del cleaned_data['i_am_sure']
return cleaned_data
class Meta:
model = Person
If you want to use this with Django admin. specify this form in your admin.py:
from django.contrib import admin
from .forms import PersonForm
from .models import Person
class PersonAdmin(admin.ModelAdmin):
form = PersonForm
admin.site.register(Person, PersonAdmin)
Note however that there's a bug with hidden inputs on Django admin forms. There's a solution to that on this Stack Overflow question.
You can add views and urls to your ModelAdmin and overwrite your modeladmin add view to redirect accordingly.
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
def my_view(self, request):
# custom view which should return an HttpResponse
pass
So, after a bit of coding I got it to work.
My modeladmin looks simular to this now
class MyModelAdmin(admin.ModelAdmin):
def get_urls(self):
urls = super(MyModelAdmin, self).get_urls()
my_urls = patterns('',
(r'^my_view/$', self.my_view)
)
return my_urls + urls
def my_view(self,request,pk):
from django.shortcuts import render_to_response
from django.template import RequestContext
object = Model.objects.get(pk=pk)
model_dict = model_object.__dict__
return render_to_response('admin/app_name/model/model_view.html',locals(),context_instance=RequestContext(request))
#csrf_protect_m
#transaction.commit_on_success
def add_view(self, request, form_url='', extra_context=None):
"The 'add' admin view for this model."
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
if all_valid(formsets) and form_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
self.log_addition(request, new_object)
log.info('The new object has %s id' % new_object.id)
return HttpResponseRedirect('/admin/draws/contest/contest_view/%s' % new_object.id) <-- changed to my new one
.................
.................
Created a html template in templates/admin/app_name/model_view.html and that is it!

Categories

Resources