Big picture: I'd like my reverse method in get_absolute_url (see below) to return a url with a query parameter appended to it at the end, e.g. <url>?foo=bar. Further, I'd like bar to be specified by the POST request that triggered the call to get_absolute_url, either as an input to the form (but not a field represented by the model, something temporary) or as a url query parameter. I am easily able to access bar in my view using either method, but I can't seem to figure out how to access it in my model.
The motivation here is that my detail page splits up the fields from my model into different tabs using javascript (think https://www.w3schools.com/howto/howto_js_tabs.asp). When the user is updating the model, they choose which tab they want to update, and then the update template only renders the fields from the model which are related to that tab. More importantly, after the user submits the update, I want the detail page to know to open the specific tab that the user just edited.
(I understand how this works if the field is a part of the model; in get_absolute_url with parameters, the solution is pretty straightforward and involves using self.id. In my case though, bar is not a part of the model and I can't figure out how else to access it)
Some specifics: I have a model in my project called Context. I have implemented a generic DetailView and an update page for the model using a modelform called ContextForm and a generic UpdateView called ContextUpdate. Once the form is submitted, I redirect to the detail page using get_absolute_url in models.py:
def get_absolute_url(self):
return reverse("context:review",kwargs={"slug": self.slug})
My urlpatterns in urls.py looks something like:
urlpatterns = [
url(r'^(?P<slug>[-\w]+)$',views.ContextDetail.as_view(),name="review"),
url(r'^(?P<slug>[\w]+)/edit$',views.ContextUpdate.as_view(),name="edit"),
]
I am able to access this parameter in my UpdateView quite easily:
def post(self,request,**kwargs):
print (request.POST.get("bar")) #accessing input to form
print (request.GET.get("bar")) #accesssing url parameter
return super().post(request,**kwargs)
But when get_absolute_url is called inside the model, it seems I no longer have access to it.
Any suggestions for how to accomplish this? I want to use get_absolute_url (along with modelforms, generic views, etc.) so that I can follow Django conventions, but it seems like using get_absolute_url is making the functionality that I want difficult to accomplish. If the redirect to the detail view following the POST request were to happen inside my view, then I would know how to solve this (I think). Any thoughts or ideas would be greatly appreciated!
As you say, you can't access the request inside your get_absolute_url method. Therefore you should override get_success_url, from which you can access it.
def get_success_url(self):
return reverse(reverse("context:review", kwargs={"slug": self.object.slug}) + '?bar=%s' % self.request.GET.get('bar')
Or if you want to re-use get_absolute_url:
def get_success_url(self):
return self.object.get_absolute_url + '?bar=%s' % self.request.GET.get('bar')
The second option is DRYer but would break if get_absolute_url was changed to include a querystring like ?foo=foo.
Related
I have a form that I want to inject into a class based DetailView.
forms.py
class PastLocationForm(forms.Form):
locations = forms.ModelChoiceField(queryset=Location.objects.all().order_by('location_name'))
views.py
class PatientDetailView(DetailView):
model=Patient
form_class = PastLocationForm
Unfortunately, the form PastLocationForm doesn't appear on the HTML page after injection. I inspected the page and there was nothing.
Interestingly, if I pass PastLocationForm to a functional view and render it for another page, the form shows up! I also have other views where I make use of "form_class" for other modelForms and they function correctly.
I will switch my view to functional view if I can't find the solution but I would rather keep the class based view.
The reason might be the fact that DetailView does not handle a form_class attribute (FormViews do), you'd need to use a mixin.
Check out this answer:
Django combine DetailView and FormView
I am using Django with crispy_forms third party library. I want to add a link beside a form field like some forms in Django admin app. How can I do this?
You've picked quite a complicated example that uses a method I wouldn't even recommend. But I'll try to explain how you see what you're seeing, and keep it short.
That is a AdminTimeWidget(forms.TimeInput) and a AdminDateWidget both nested in a AdminSplitDateTime(SplitDateTimeWidget(MultiWidget)). The MultiWidget part of this isn't really important, that's just how you bind two widgets together to provide one value (a datetime.datetime).
Here's what AdminTimeWidget looks like:
class AdminTimeWidget(forms.TimeInput):
#property
def media(self):
extra = '' if settings.DEBUG else '.min'
js = [
'vendor/jquery/jquery%s.js' % extra,
'jquery.init.js',
'calendar.js',
'admin/DateTimeShortcuts.js',
]
return forms.Media(js=["admin/js/%s" % path for path in js])
def __init__(self, attrs=None, format=None):
final_attrs = {'class': 'vTimeField', 'size': '8'}
if attrs is not None:
final_attrs.update(attrs)
super().__init__(attrs=final_attrs, format=format)
That adds a DateTimeShortcuts.js script to the page (in the way that Admin Widgets can, via the form media property) and it's that script that iterates input tags looking for date and time inputs.
There's a LOT of machinery involved to get that happening but again, in effect, it's just a bit of javascript that looks for a date/time input and adds the HTML client-side.
But you probably don't want to do that.
As I said, that's a very complicated widget, and in Admin where it's harder to alter things on the fly. If you want to write an Admin widget, you probably do want to go that way.
But if you already control the template, or a crispy layout, you could just bung in some HTML. Crispy has an HTML element that you can throw into layouts. This is well documented.
Or if you want a reusable widget, you could use a custom template. Since Django 1.11, Widgets use templates to render.
Create a widget, borrowing from an existing one to save time
from django.forms import widgets
class DateWithButtonWidget(widgets.DateInput):
template_name = 'widgets/date_with_button.html'
Customise the template with the HTML you want:
{% include "django/forms/widgets/input.html" %} <button>MY BUTTON</button>
Use that widget in your form:
class MyForm(forms.ModelForm):
fancydate = forms.DateField(widget=DateWithButtonWidget)
Of course, wiring that button to do something is all up to you. Using a fully-scripted option might be what you need after all.
I have a blog where i would like to construct my URLs in following fashion: blah.com/blog/this-is-my-first-blog-post where this-is-my-first-blog-post is the title of one specific blog post.
Is there a way to generate these types of urls based on the title column in my table with blogposts?
You'll be better off storing the URL key as a SlugField. You can determine that value by using slugify.
Then your code for the url would be something like:
url(r'^(?P<slug>[-\w]+)/$','example_view'),
And you can use get_object_or_404:
def example_view(request, slug):
instance = get_object_or_404(Model, slug=slug)
Or you can use a DetailView CBV as shown in the docs.
There are a couple of options:
In django admin, you can make the field pre-populate on editing.
prepopulated_fields = {'slug': ('title',), }
This needs to be put in the admin.py file.
In case you want to do it outside of admin, you can use a pre-save signal and use slugify as mentioned above by #schillingt above.
I'm trying to manage my REST API like that :
http://xxx/users/userid[0-9]+/projects/projectid[0-9]+/tasks/taskid[0-9]+/
So I can access the JSON easily in my website. But, the thing is, I defined my view classes using the REST framework generic views. For example, here is my UserDetail view :
class UserDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
But of course I don't want all my users to be displayed, I just want my user with the ID userid to be displayed. I don't really know how to do it, I tried
queryset = User.objects.filter(id=userid)
but of course userid is not defined... Any help please ?
Edit : just to clarify, here is the url that leads to this view :
url(r'^users/(?P<pku>[0-9]+)/$', views.UserDetail.as_view(
), name='user-detail'),
First of all, If you want to use class based views, you should read some more about them. They are thoroughly explained on Django's docs and you can read about the specific generics of the framework you're using on the REST framework docs too. I'm not saying that you haven't read those, just that you seem to be missing some basic concepts that are explained there.
Now, to the problem at hand, if you look at the doc's of the generic view you're extending, you can see that it represents the endpoints for a single instance, meaning that it won't act on all your model's instances (as you seem to assume).
Also, you can see that this view is built on top of a series of other classes, the more critical one being GenericAPIView. On there you can see two things:
The queryset class field in this context is meant for filtering the posible instances you can manipulate, not obtaining the specific instance described on your url.
The lookup_field is the field that defines which attribute from your model will be used for getting the actual instance. You should define this field to whatever field you're going to use on your url to identify your object (generally it's pk). It's also important to note that the url should include a keyword argument corresponding to this value.
Internally, the view will take care of calling the get_object method, which usese the lookup_field value to find the specific model, and then feed that object to the serializer and return the result back to the client.
DISCLAIMER: I've never used Django REST framework, I put this answer togheter by reading the relevants docs and based on my experience.
I guess you need this:
Resolve function (django 1.4)
Then in your view class method you can do:
temp1, args, kwargs = resolve(self.request.path)
I am using the Django comments framework to allow commenting on articles in a blog. I want to display the title of the article that the comment(s) belongs to in the list view of the comments section where the comment name, content type, object id etc is.
How do I do this? I know you can hook up actions into your admin.py list view by writing a model method but in this case I do not have a model for comments since I am using the built in one.
Thanks
Somewhere in your code you can override the Comments ModelAdmin class and extend it to do what you want. This code isn't tested, but it should give you a general enough idea about how to customize the Comment admin:
from django.contrib import admin
from django.contrib.comments.admin import CommentsAdmin
class MyCommentsAdmin(CommentsAdmin):
# The callable that the list_display will call
def show_object_title(self):
return self.content_object.title
list_display = super(MyCommentsAdmin, self).list_display
list_display += ('show_object_title',)
admin.site.unregister(Comment)
admin.site.register(Comment, MyCommentsAdmin)
This doesn't work because the method gets two parameters and you have to use the second to get the title. This works:
def show_object_title(self, obj):
return obj.content_object.title
Also the super()-call doesn't work here. At least not the way described above. It may be easier to just copy and edit the list_display-tuple from the Comments-Admin-Source