Django Sitemap Framework. Accepting query parameters - python

I'm using the Django Sitemap Framework
I've no problem retrieving a list of articles from my DB.
class ArticleSitemap(Sitemap):
def items(self):
return articles.objects.filter(tagid=1399).order_by('-publisheddate')
I now want to accept a query parameter to filter by an inputted tag id ie:
sitemap.xml?tagid=1000
I have yet to find an example in the docs or on stack.

It is not possible to access HttpRequest object from Sitemap class. Probably the easiest way is to create your own view(s) for the sitemap(s), do what you need to do with HttpRequest and call Django internal view to do the final rendering of XML.
Setup your sitemap URLs as Django docs says (https://docs.djangoproject.com/en/dev/ref/contrib/sitemaps/#initialization), but use your own view(s).
urls.py:
from my_app.sitemap_views import custom_sitemap_index, custom_sitemap_section
sitemaps = {
"foo": FooSitemap,
"bar": BarSitemap,
}
urlpatterns = [
url(
r"^sitemap\.xml$",
custom_sitemap_index,
{"sitemaps": sitemaps},
name="sitemap_index",
),
url(
r"^sitemap-(?P<section>.+)\.xml$",
custom_sitemap_section,
{"sitemaps": sitemaps},
name="sitemaps",
),
# ...
]
Your custom sitemap views are standard Django views: you can access HttpRequest, database, cache...
sitemap_views.py:
import copy
from django.contrib.sitemaps import views as django_sitemaps_views
from django.contrib.sitemaps.views import x_robots_tag
#x_robots_tag
def custom_sitemap_index(
request,
sitemaps,
template_name="sitemap_index.xml",
content_type="application/xml",
sitemap_url_name="django.contrib.sitemaps.views.sitemap",
):
print("You can access request here.", request)
return django_sitemaps_views.index(
request, template_name, content_type, sitemaps, sitemap_url_name
)
#x_robots_tag
def custom_sitemap_section(
request,
sitemaps,
section=None,
template_name="sitemap.xml",
content_type="application/xml",
):
tag_id = int(request.GET.get("tagid"))
# We do not want to modify global variable "sitemaps"!
# Otherwise sitemap instances would be shared across requests (tag_id should be dynamic).
sitemaps_copy = copy.deepcopy(sitemaps)
for section, site in sitemaps_copy.items():
if callable(site):
sitemaps_copy[section] = site(tag_id=tag_id)
return django_sitemaps_views.sitemap(
request, sitemaps_copy, section, template_name, content_type
)
sitemap.py:
from django.contrib.sitemaps import Sitemap
class FooSitemap(Sitemap):
def __init__(self, tag_id: int):
self.tag_id = tag_id
super().__init__()
def items(self):
return (
Articles.objects.filter(tagid=1399)
.filter(tag_id=self.tag_id)
.order_by("-publisheddate")
)
class BarSitemap(Sitemap):
pass
# ...
# ...

Its in the request's Get-attribute:
the url '.../names/getNames?pattern=Helm' results in a request-object that has as GET : &LT;QueryDict: {'pattern': ['Helm']}&GT;

Related

Empty `request.user.username` while handling a GET request created

I was trying out logging all URLs accessed by user along with user id and date time when it was accessed using django middleware as explained here.
For some URLs it was not logging user id. I checked and found that the request.user.username was empty string. I checked views corresponding to those URL and found that those views did not have desired decorators. For example, I changed this:
def getXyz_forListView(request):
# view body ...
to this:
#api_view(['GET'])
#authentication_classes([TokenAuthentication,])
def getXyz_forListView(request):
# view body ...
and it started working.
However some views are created from classes:
class XyzView(View):
def get(self, request):
# view body ...
I added same decorators:
class XyzView(View):
#api_view(['GET'])
#authentication_classes([TokenAuthentication,])
def get(self, request):
# view body ...
But it is still not working. What I am missing?
PS:
It is added to urls.py as follows:
urlpatterns = [
# ...
url(r'^xyz/', XyzView.as_view(), name="xyz"),
]
I think you should try to inherit from APIView class:
from rest_framework.views import APIView

Django URL dispatcher - Try next view

Alright, let me give you guys an example;
We have the following url configuration in Django.
Django will try to match the url with the rules down below. Once it finds a match, it will use the appropriate view and lookup the object in the model.
The thing is, once it finds a match in the URL pattern, it will match the view. But once the object
in the view can't be found, it will return a page not found (404) error.
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<slug:category>/<slug:editor>/', views.ArticleByThemeView.as_view(), name='articles_by_editor'),
path('articles/<slug:category>/<slug:theme>/', views.ArticleDetailView.as_view(), name='articles_by_theme')
]
views.py
class ArticleByThemeView(ListView):
"""
List all articles by a certain theme; "World War 2".
"""
model = Article
def dispatch(self, request, *args, **kwargs):
try:
# Check if the theme_slug matches a theme
theme = ArticleTheme.objects.get(slug=self.kwargs['theme_slug'])
except ArticleTheme.DoesNotExist:
# Theme does not exist, slug must be an article_slug
return redirect(
'article_detail',
category_slug=category_slug
article_slug=theme_slug
)
return super().dispatch(request, *args, **kwargs)
class ArticleDetailView(DetailView):
"""
Detailview for a certain article
"""
model = Article
def get_object(self):
return get_object_or_404(
Article,
category__slug=self.kwargs['category_slug'],
slug=self.kwargs['article_slug']
)
We have the following url patterns, we can sort articles either by the editor or by theme. We do this to create a logical url structure for SEO purposes.
Is their any way we can redirect to another view once the object isn't found?
Can we modify the dispatch method to return to the url patterns and find the following matching rule?
What about redirection like this:
def articles_by_editor(request, category, editor):
try:
article = Article.objects.get(category=category, editor=editor)
# return article
except Article.DoesNotExist:
# redirect to another view
return redirect('articles_by_theme', category=category)
Alright,
Based on the suggestion from Sunderam Dubey, I'wrote a function view, which uses two differn routes to the same view.
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('articles/<slug:category>/<slug:slug>/', views.article_theme_or_detail_view, name='article_by_theme'),
path('articles/<slug:category>/<slug:slug>/', views.article_theme_or_detail_view, name='article_detail')
]
views.py
def article_theme_or_detail_view(
request,
category_slug,
slug=None
):
"""
This view could either be for a theme view or detailview,
depending on the slug.
"""
try:
# Check if the slug represents a theme
theme = ArticleTheme.objects.get(slug=slug)
article_list = Article.object.filter(theme=theme)
# Add context
context = {
'theme': theme,
'article_list': article_list
}
# Render the template with context
return render(
request,
'article_by_theme.html',
context
)
except ArticleTheme.DoesNotExists:
# The theme does not exist so the slug must be for a detail view
context = {
article = Article.objects.get(slug=slug)
}
return render(
request,
'article_detail.html',
context
)
Todo:
Remove one of the url routes

Customize django admin. Problem with no-model views

Im trying to create a extra view in django admin, on the left navbar. This view will be responsible for uploading a file, which will be parsed in some function (in future i would like to render result of this parsing in admin page). This file wont be saved in database, so there wont be a model. Is there any possibility to add a view to django admin (left navbar) which dont have a model? I was reading a lot, and could find a solution. What i have done for now:
Created a class which inherits from AdminSite. I tried to implement get_app_list method, but variable self._build_app_dict(request) was empty array, and this means, method couldn't find a installed aps. I wanted to add new object to app_list variable, to render it on website.
Tried to override a admin templates, but couldnt render it. I tried to override app_index.html which i put on folder: app_name/templates/admin/app_index.html
Here is my code, which ofc doesnt work:
class MyCustomAdmin(AdminSite):
def get_app_list(self, request):
"""
Return a sorted list of all the installed apps that have been
registered in this site.
"""
app_dict = self._build_app_dict(request)
breakpoint()
app_list = sorted(app_dict.values(), key=lambda x: x['name'].lower())
for app in app_list:
app['models'].sort(key=lambda x: x['name'])
return app_list
def get_urls(self):
from django.conf.urls import url
urls = super(MyCustomAdmin, self).get_urls()
urls += [
url(r'^my_custom_view/$', self.admin_view(MyCustomView.as_view()))
]
return urls
class MyCustomView(View):
template_name = 'admin/app_index.html'
def get(self, request):
print('fefef')
return render(request, self.template_name, {})
def post(self, request):
pass
admin_site = MyCustomAdmin()
admin_site.get_app_list(AdminSite.get_app_list)

How to do bulk instance deletion in Django Rest Framework?

In DRF's DefaultRouter url router, it requires a {lookup} parameter to route DELETE requests to the destroy method of a ModelViewSet (so, you'd make your request to delete an object instance to the endpoint {prefix}/{lookup}/).
This is fine for deleting a single instance, but I'd like to extend that functionality to deleting multiple instances on a single request. Let's say the lookup parameter is called uuid and the model is called Product. Here's an extended version of destroy:
def destroy(self, request, uuid=None):
"""
Overridden method allows either url parameter of single UUID
(to delete a single instance), or multiple query parameters `uuids`
to delete multiple instances.
"""
if not uuid:
uuids = request.query_params.get('uuids', None)
if not uuids:
return Response(status=status.HTTP_404_NOT_FOUND)
if len(uuids) != Product.objects.filter(uuid__in=uuids).count():
return Response(status=status.HTTP_404_NOT_FOUND)
Product.objects.filter(uuid__in=uuids).delete()
else:
instance = self.get_object(uuid)
if not instance:
return Response(status=status.HTTP_404_NOT_FOUND)
instance.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
So this version takes a DELETE request and multiple uuids[] query parameters in the url. Now I just need to route it in urls.py:
from rest_framework.routers import DefaultRouter, Route
class BulkDeleteRouter(DefaultRouter):
"""
a custom URL router for the Product API that correctly routes
DELETE requests with multiple query parameters.
"""
def __init__(self, *args, **kwargs):
super(BulkDeleteRouter, self).__init__(*args, **kwargs)
self.routes += [
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={'delete': 'destroy'},
name='{basename}-delete',
initkwargs={'suffix': 'Delete'}
),
]
bulk_delete_router = BulkDeleteRouter()
bulk_delete_router.register(r'product', ProductViewSet, base_name='product')
This, unfortunately, has killed my url router. It won't resolve GET to the appropriate methods in the viewset, and I don't understand why - isn't my BulkDeleteRouter supposed to extend this functionality from the DefaultRouter? What did I do wrong?
Forgot to add the router urls to the urlpatterns. I must be blind.
urlpatterns += [
url(r'^API/', include(bulk_delete_router.urls, namespace='api')),
]
Adding an additional 'delete': 'destroy' to the 'List route' route will perfectly do the job.
class CustomRouter(DefaultRouter):
"""
a custom URL router for the Product API that correctly routes
DELETE requests with multiple query parameters.
"""
routes = [
# List route.
Route(
url=r'^{prefix}{trailing_slash}$',
mapping={
'get': 'list',
'post': 'create',
'delete': 'destroy', # The magic
},
name='{basename}-list',
detail=False,
initkwargs={'suffix': 'List'}
),
# Dynamically generated list routes. Generated using
# #action(detail=False) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=False,
initkwargs={}
),
# Detail route.
Route(
url=r'^{prefix}/{lookup}{trailing_slash}$',
mapping={
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy'
},
name='{basename}-detail',
detail=True,
initkwargs={'suffix': 'Instance'}
),
# Dynamically generated detail routes. Generated using
# #action(detail=True) decorator on methods of the viewset.
DynamicRoute(
url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
name='{basename}-{url_name}',
detail=True,
initkwargs={}
),
]
Then use the router like this:
custom_router = CustomRouter()
custom_router.register(r'your-endpoint', YourViewSet)
urlpatterns = [
url(r'^', include(custom_router.urls)),
]
The viewset:
from rest_framework import viewsets, status
from rest_framework.response import Response
from django.db.models import QuerySet
class MachineSegmentAnnotationViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs):
qs: QuerySet = self.get_queryset(*args, **kwargs)
qs.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
Hope this helps.

Django 1.9 : Passing arguments from urls.py to a class view

I am creating a small web application as a mini project of mine to learn the Django framework. I'm on Version 1.9.4, on OS X. I'm trying to pass a string in the URL that will be sent to a class-based view, and it will return a different template based on the URL. To my knowledge, doing (?P) will allow the input of dynamic text. \w is for characters, and writing <name> will pass it as a variable. Is this configured right, or is this is not the correct way to do it?
The reason I'm concerned is that the Django documentation uses method views, while I am using class-based views.
urls.py
from django.conf.urls import url
from . import views
app_name = 'xyz'
urlpatterns = [
url(r'^create/(?P<ty>\w+)$', views.ArticleView.as_view(), name='article-form'), #.as_view() to turn Class into View
]
views.py
class ArticleCreate(View):
l = {
'weapon': WeaponForm,
'map': MapForm,
'operator': OperatorForm,
'gadget': GadgetForm,
'skin': SkinForm
}
ty = ty.lower()
template_name = 'xyz/create_article_form.html'
def get(self, request):
return render(request, self.template_name)
def post(self, request):
pass
The arguments that are being passed to the url should be "catched" within the view inside the relevant function, for example:
def get(self, request, ty):
ty = ty.lower()
return render(request, self.template_name)

Categories