This is a continuation of my other question about how to include additional queryset (with request) in an app (Userena) view. If I do what #limelights suggested, this is what my code looks like:
view:
from django.views.generic import list_detail
def requestuserswampers(request):
qs = Thing.objects.filter(user=request.user)
return list_detail.object_list(
request,
queryset = Thing.objects.all(),
template_object_name = 'thing',
extra_context = {'swamp_things': qs},
)
url:
url(r'^accounts/(?P<username>(?!signout|signup|signin)[\.\w-]+)/$',
requestuserswampers,
name='userena_profile_detail'),
This generates a TemplateDoesNotExist error: Template does not exist at myapp/swamp_things.html.
If I try to include the template name and location using template_name = 'userena/profile_detail.html', on the other hand, the right template is rendered, but some of the context is now missing, like the user information that is normally rendered in the default userena "profile_detail" template..
How do I add an extra queryset to the Userena profile detail view which allows for request so that I can filter objects based on the logged in user? Thanks for your ideas!
I didn't realize that it was accepted practice to re-write another app's view, figuring that it was antithetical to DRY principles. But since I had not discovered another method of achieving what I needed to do, and since it was endorsed by another senior user in the comments above, I went ahead and tried to re-write the Userena view. Just needed to add my queryset into the extra_context:
def profile_detail(request, username,
template_name=userena_settings.USERENA_PROFILE_DETAIL_TEMPLATE,
extra_context=None, **kwargs):
user = get_object_or_404(get_user_model(),
username__iexact=username)
profile_model = get_profile_model()
try:
profile = user.get_profile()
except profile_model.DoesNotExist:
profile = profile_model.objects.create(user=user)
if not profile.can_view_profile(request.user):
return HttpResponseForbidden(_("You don't have permission to view this profile."))
if not extra_context: extra_context = dict()
extra_context['profile'] = user.get_profile()
extra_context['hide_email'] = userena_settings.USERENA_HIDE_EMAIL
#### Added the line below
extra_context['swamp_things'] = Thing.objects.filter(user=user)
return ExtraContextTemplateView.as_view(template_name=template_name,
extra_context=extra_context)(request)
Related
I am able to render class based view generic ListView template using parameter hard coded in views.py.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
# def get(self, request):
# if request.GET.get('q'):
# query = request.GET.get('q')
# print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
However, when parameter is sent via form by GET method (below),
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
query = 'theory'
def get(self, request):
if request.GET.get('q'):
query = request.GET.get('q')
print(query)
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
I receive this error
The view creations.views.ResourceSearchView didn't return an
HttpResponse object. It returned None instead.
Note that the parameter name q and associated value is being retrieved successfully (confirmed using print(query)).
So with CBV in Django, you have to return some kind of valid response that the interpreter can use to perform an actual HTTP action. Your GET method isn't returning anything and that's what is making Django angry. You can render a template or redirect the user to a view that renders a template but you must do something. One common pattern in CBV is to do something like:
return super().get(request, *args, **kwargs)
...which continues up the chain of method calls that ultimately renders a template or otherwise processes the response. You could also call render_to_response() directly yourself or if you're moving on from that view, redirect the user to get_success_url or similar.
Have a look here (http://ccbv.co.uk) for an easy-to-read layout of all the current Django CBVs and which methods / variables they support.
Thanks for the responses. Here is one solution.
class ResourceSearchView(generic.ListView):
model = creations
context_object_name = 'reviews'
template_name = 'reviews.html'
def get_queryset(self):
query = self.request.GET.get('q')
queryset = creations.objects.filter(narrative__contains=query).order_by('-post_date')
return queryset
I'm new to the Django Framework and one thing bothers me.
I want a simple Rest Call:
www.abc.com/users/1/cantonments/1/
If i use 'pk' in the url pattern everything works out of the box (pk, pk1, pk2....).
But i have some permission functionality which expects the parameters in kwargs in the form 'upk' and 'cpk' for user and cantonment. So if i change pk to upk everything breaks. Somehow the url needs ONE pk.
This works:
url(r'^users/(?P<pk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
This doesnt:
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
Is there any way to have an url pattern that does not need one entry with pk?
P.S. The error:
Expected view CantonmentDetail to be called with a URL keyword argument named "pk". Fix your URL conf, or set the `.lookup_field` attribute on the view correctly.
EDIT:
My view is simple:
# Authenticated User can show Cantonment Detail
class CantonmentDetail(generics.RetrieveAPIView):
serializer_class = serializers.CantonmentSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
return Cantonment.objects.filter(pk=self.kwargs['cpk'])
Edit2:
I changed get_queryset to get object and it works.
def get_object(self):
queryset = self.filter_queryset(self.get_queryset())
obj = queryset.get(pk=self.kwargs['cpk'])
return obj
Edit3:
Using
lookup_url_kwarg = "cpk"
in the class works as well.
You can send optional pk using get method with your url like
www.abc.com/users/1/cantonments/?&upk=1
and url should be
url(r'^users/(?P<pk>[0-9]+)/cantonments/$',
views.CantonmentDetail.as_view()),
and views.py
def view_name(request, pk=None):
upk = request.GET.get('upk')
May be in your view you are accessing pk variable
urls.py
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
views.py
class your_class_name(ListView):
def view_name(self):
upk=self.kwargs['upk']
cpk=self.kwargs['cpk']
print upk, cpk
...
Hope this is helps you
The upk doesn't make any difference to the lookup (because a primary key identifies a single object by design).
So for the view, the lookup_field needs to be set to 'cpk' and everything works.
Did you changed your view with the new names of the variables?
If you have url like this:
url(r'^users/(?P<upk>[0-9]+)/cantonments/(?P<cpk>[0-9]+)/$',
views.CantonmentDetail.as_view()),
You shouls update your view like this:
def view_name(request, upk=None, cpk=None):
...
I need to get a changelist view queryset in django admin. Currently, I have this monkey patch which makes 4 extra queries, so I'm looking for a better solution.
My point is: I want to pass some extra values to django admin change_list.html template which I get from creating queries. For those queries, I need the queryset which is used in django admin changelist view with request filters applied. This is the same data which I see filtered, ordered etc. I want to make graphs from this data.
Do you understand me? Thanks
#admin.py
from django.contrib.admin.views.main import ChangeList
class TicketAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
cl = ChangeList(request,
self.model,
self.list_display,
self.list_display_links,
self.list_filter,
self.date_hierarchy,
self.search_fields,
self.list_select_related,
self.list_per_page,
self.list_max_show_all,
self.list_editable,
self) # 3 extra queries
filtered_query_set = cl.get_query_set(request) # 1 extra query
currencies_count = filtered_query_set.values('bookmaker__currency').distinct().count()
extra_context = {
'currencies_count': currencies_count,
}
return super(TicketAdmin, self).changelist_view(request, extra_context=extra_context)
I don't know if this answers to your question but the class ChangeList has an attribute called query_set (you can find the code here https://github.com/django/django/blob/master/django/contrib/admin/views/main.py) already containing the queryset.
BTW the changelist_view() function (source at https://github.com/django/django/blob/master/django/contrib/admin/options.py) returns a TemplateResponse (source at https://github.com/django/django/blob/master/django/template/response.py) that has a variable named context_data which points to the context. You can try to extend the content of this variable.
Below follows the untested code
class TicketAdmin(admin.ModelAdmin):
def changelist_view(self, request, extra_context=None):
response = super(TicketAdmin, self).changelist_view(request, extra_context)
filtered_query_set = response.context_data["cl"].queryset
currencies_count = filtered_query_set.values('bookmaker__currency').distinct().count()
extra_context = {
'currencies_count': currencies_count,
}
response.context_data.update(extra_context)
return response
I am converting a WordPress site to a django one. I need to preserve the url structure for old posts, but have a different structure for new posts. I've done this by creating the 2 urls, setting a date in settings.py, then setting the absolute url like so:
urls.py
url(r'^reviews/archives/(?P<pk>\d+)$', PostDetail.as_view(), name="oldpost_view"),
posts/urls.py
url(r'^(?P<slug>[-\w]+)$', PostDetail.as_view(), name="post_view"),
posts/models.py
#property
def is_old_post(self):
wp_date = settings.WP_ARCHIVE_DATE
if self.post_date.date() < wp_date:
return True
# return False
#models.permalink
def get_abs_url(self):
if self.is_old_post:
return ('oldpost_view', (), {
'pk': self.id,
}
)
else:
return ('post_view', [str(self.url_slug)])
I am using one view for the 2 urls:
class PostDetail(DetailView):
model = Post
slug_field = 'url_slug'
template_name = "posts/detail.html"
This all works great. Now, what I need to is prevent new posts from being rendered by the oldpost_view url and vice versa. I know I can override the "get" and use reverse for this but how can I tell which url the request came from? What is the most efficient and DRY way to do this?
If you don't follow my advice with the '301' status code above, here is how I would do it:
Override the get method on the DetailView
If the date is before CUTOFF_DATE, and request.path[:10] != "reviews/arc" --> Redirect (301)
elseif date is after CUTOFF_DATE and request.path[:10] == "reviews/arc" --> redirect
Something roughly like that.
Based on Issac Kelly's feedback I was able to solve my problem. Here's the updated views:
class PostDetail(DetailView):
model = Post
slug_field = 'post_name'
template_name = "posts/detail.html"
def OldPostView(request, pk):
post_name = get_object_or_404(Post, pk=pk).post_name
return redirect('post_view', slug=post_name, permanent=True)
I also updated my models to utilize the "post_name" field that WordPress has, then simplified my permalink:
#models.permalink
def get_abs_url(self):
return ('post_view', [str(self.post_name)])
Thanks Issac!
I'm currently learning how to use the class-based views in django 1.3. I'm trying to update an application to use them, but I still don't uderstand very well how they work (and I read the entire class-based views reference like two or three times EVERY day).
To the question, I have an space index page that needs some extra context data, the url parameter is a name (no pk, and that can't be changed, it's the expected behaviour) and the users that don't have that space selected in their profiles can't enter it.
My function-based code (working fine):
def view_space_index(request, space_name):
place = get_object_or_404(Space, url=space_name)
extra_context = {
'entities': Entity.objects.filter(space=place.id),
'documents': Document.objects.filter(space=place.id),
'proposals': Proposal.objects.filter(space=place.id).order_by('-pub_date'),
'publication': Post.objects.filter(post_space=place.id).order_by('-post_pubdate'),
}
for i in request.user.profile.spaces.all():
if i.url == space_name:
return object_detail(request,
queryset = Space.objects.all(),
object_id = place.id,
template_name = 'spaces/space_index.html',
template_object_name = 'get_place',
extra_context = extra_context,
)
return render_to_response('not_allowed.html', {'get_place': place},
context_instance=RequestContext(request))
My class-based view (not working, and no idea how to continue):
class ViewSpaceIndex(DetailView):
# Gets all the objects in a model
queryset = Space.objects.all()
# Get the url parameter intead of matching the PK
slug_field = 'space_name'
# Defines the context name in the template
context_object_name = 'get_place'
# Template to render
template_name = 'spaces/space_index.html'
def get_object(self):
return get_object_or_404(Space, url=slug_field)
# Get extra context data
def get_context_data(self, **kwargs):
context = super(ViewSpaceIndex, self).get_context_data(**kwargs)
place = self.get_object()
context['entities'] = Entity.objects.filter(space=place.id)
context['documents'] = Document.objects.filter(space=place.id)
context['proposals'] = Proposal.objects.filter(space=place.id).order_by('-pub_date')
context['publication'] = Post.objects.filter(post_space=place.id).order_by('-post_pubdate')
return context
urls.py
from e_cidadania.apps.spaces.views import GoToSpace, ViewSpaceIndex
urlpatterns = patterns('',
(r'^(?P<space_name>\w+)/', ViewSpaceIndex.as_view()),
)
What am I missing for the DetailView to work?
The only problem I see in your code is that your url's slug parameter is named 'space_name' instead of 'slug'. The view's slug_field attribute refers to the model field that will be used for slug lookup, not the url capture name. In the url, you must name the parameter 'slug' (or 'pk', when it's used instead).
Also, if you're defining a get_object method, you don't need the attributes queryset, model or slug_field, unless you use them in your get_object or somewhere else.
In the case above, you could either use your get_object as you wrote or define the following, only:
model = Space
slug_field = 'space_name'