I'm creating a Restful API with Django REST Framework. What I have is a project with two different apps. One app is called project/catalog and one is called project/environment. Both of the apps have a model called Device and their own url.py file, which will be included in the project/url.py file.
The urls from the apps are included with a namespace in project/url.py:
# project/url.py
urlpatterns = [
path(r'api/', include(('environment.urls', 'environment'))),
path(r'api/', include(('catalog.urls', 'catalog')))
]
The basename for the urls are automatic generated because in the viewSet I have a attribute queryset
My question is:
Is it possible to override the automatic view_name generation to use the pattern with a namespace? What it says in the documentation is that the view_name is generated like this (model_name)-detail for example. What I want is that the view_name will be generated like this (app_name):(model_name)-detail
I can use the namespace when I create the HyperlinkedIdentityField by my self and add the view_name attribute, like below:
# project/catalog/serializer.py
class DeviceSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='catalog:device-detail')
class Meta:
model = models.Device
fields = [
'url',
...
# project/environment/serializer.py
class DeviceSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(view_name='environment:device-detail')
class Meta:
model = models.Device
fields = [
'url',
...
But once I remove the example above it throws the Could not resolve URL for hyperlinked relationship using view name "device-detail" exception. Because the automatic generated view_name excpects device-detail, but I want it to expect catalog:device-detail for the catalog app or environment:device-detail for the environment app.
Some solutions I know about are:
Removing the namespace in project/url.py, but then there are two apps urls which has the same basename. In project/catalog the basename will be device-detail as same for project/environment. This will give issues because it will only look at one basename.
Change the HyperlinkedModelSerializer to just ModelSerializer, but that doesn't really fix my problem for my use case
Define every HyperlinkedIdentityField and add the view_name for every serializer, but that will be a pain for maintenance
My app url files
# project/catalog/url.py
from django.urls import include, path
from rest_framework import routers
from catalog import views
router.register(r'devices', views.DeviceViewSet)
urlpatterns = [
path('catalog/', include(router.urls)),
]
# project/environment/url.py
from django.urls import include, path
from rest_framework import routers
from environment import views
router.register(r'devices', views.DeviceViewSet)
urlpatterns = [
path('environment/', include(router.urls)),
]
EDIT
I have for example the AssemblySerializer in the app environment project/environment/serializer.py. Because I have two apps which can have the same urls I have to use namespaces, which is OK and what I want.
class AssemblySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Assembly
fields = '__all__'
extra_kwargs = {
'url': {'view_name': 'environment:installedassembly-detail'},
'device': {'view_name': 'environment:device-detail'},
'parent': {'view_name': 'environment:installedassembly-detail'},
'catalog_assembly': {'view_name': 'catalog:assembly-detail'}
}
What I don't want is to set for each serializer the view_name like in the example above. I want that it will automatic look at the namespace of the app environment so that I only have to define a view_name when its outside the app, like the example below.
class AssemblySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = models.Assembly
fields = '__all__'
extra_kwargs = {
'catalog_assembly': {'view_name': 'catalog:assembly-detail'}
}
You want to use namespaces in this case.
If you are using recent Django then you should specify an app namespace in your urls.py using app_name="catalog". You can override this in include() if needed.
urlpatterns = [
path(r'api/environment/', include('environment.urls')),
path(r'api/catalog/', include('catalog.urls')),
]
Your urls.py can then be simplified like this:
app_name = 'catalog'
urlpatterns = router.urls
And you can reverse the lookup using that namespace (they can also be nested). Read up on setting the view_name on HyperlinkedModelSerializer:
class AccountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Account
fields = ['account_url', 'account_name', 'users', 'created']
extra_kwargs = {
'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
'users': {'lookup_field': 'username'}
}
If you need to add custom urls outside the router, then just add them as normal.
urlpatterns += [
path('other/', some_view_func, )
path('ship/', include(("other_thing.urls", "ship"))
]
I highly recommend you install django_extensions installed so you can view your routes:
$> ./manage.py show_urls | column -t
...
PATH VIEW FUNC VIEW NAME
/catalog/items/ app.ItemViewSet catalog:item-list
/catalog/items/<pk>/ app.ItemViewSet catalog:item-detail
...
The include function is a little strange, I'll give you that. I have a helper that I use which makes it consistent, and usually removes the need for routers:
urlpatterns = [
route('layouts/', LayoutViewSet, name="layouts"),
route('admin/', admin_urls, name='admin'),
route('catalog/', 'apps.catalog.urls', name='catalog'),
etc
]
Related
I have two apps using same names in my django project. After configuring namespacing, I still get collision. For example when I visit localhost:8000/nt/, I get template from the other app. (localhost:8000/se/ points to the right template).
I must have missed something. Here is the code:
dj_config/urls.py
urlpatterns = [
path("se/", include("simplevent.urls", namespace="se")),
path("nt/", include("nexttrain.urls", namespace="nt")),
# ...
]
dj_apps/simplevent/urls.py
from . import views
app_name = "simplevent"
urlpatterns = [
path(route="", view=views.Landing.as_view(), name="landing")
]
dj_apps/nexttrain/urls.py
from django.urls import path
from . import views
app_name = "nexttrain"
urlpatterns = [
path(route="", view=views.Landing.as_view(), name="landing"),
]
dj_config/settings.py
INSTALLED_APPS = [
"dj_apps.simplevent.apps.SimpleventConfig",
"dj_apps.nexttrain.apps.NexttrainConfig",
# ...
]
TEMPLATES = [
{
# ....
"DIRS": [],
"APP_DIRS": True,
}
Both views will have the same code:
class Landing(TemplateView):
template_name = "landing.html"
Templates are located in:
dj_apps/simplevent/templates/landing.html
dj_apps/nexttrain/templates/landing.html
Note that reversing order of apps in INSTALLED_APPS will reverse the problem (/se will point to nexttrain app).
The usual structure that your template backend expects when looking for template files is: <your app>/templates/<your app>/some_template.html, so your template paths should be:
dj_apps/simplevent/templates/simplevent/landing.html
dj_apps/nexttrain/templates/nexttrain/landing.html
Django does it like this because when collectstatic is run, it practically copies the files from templates folder of each app and places it in one static folder. This is why the app name needs to be twice in the path. Because in the final folder you end up with the app names already there. Also, you could have in your app overridden templates from third party apps (e.g. admin). It sounds a bit too convoluted for me too, but I'm pretty sure that is the intended way you're supposed to separate templates.
Also, in your view, you need to have the app name when specifying the template_name option:
# simplevent app
class Landing(TemplateView):
template_name = "simplevent/landing.html"
# nexttrain app
class Landing(TemplateView):
template_name = "nexttrain/landing.html"
PS. the same principle is applied to static folders.
im a beginner with Django and basically want to add a section in Django Admin where if I click on it, I have a view that can process data.
For example: I have models Campaign and Components which I can access through Django Admin and now I also want an Option "Analytics" that work the same way as if it was a model. Maybe this image will make clear what I want:
When I click on "Analytics" I want to call a view that can fill a html template with data that I calculate from my models e.g. build a table that counts my instances of Campaigns and Components.
What I've done so far:
create a custom AdminSite to overwrite the default AdminSite. Problem: I do not see my option appearing.
#admin.py
class MyAdminSite(admin.AdminSite):
def get_urls(self):
urls = super(MyAdminSite, self).get_urls()
custom_urls = [
path(r'myapi/test/', self.admin_view(testview)),
]
return urls + custom_urls
site_header = "My custom site"
admin_site = MyAdminSite(name="myadmin")
#apps.py
class MyAdminConfig(AdminConfig):
default_site = "myapi.admin.MyAdminSite"
#settings.py
INSTALLED_APPS = [
'rest_framework',
#'django.contrib.admin',
"myapi.apps.MyAdminConfig",
#views.py
#api_view(["GET", "POST"])
def testview(request):
print("testmyview")
return render(1, "mytest.html")
#urls.py
urlpatterns = [
path('admin/', admin_site.urls),
path('api/', include('myapi.urls')),
]
I tried to follow this tutorial:
https://adriennedomingus.medium.com/adding-custom-views-or-templates-to-django-admin-740640cc6d42
but I think after overwriting the get_urls() I should see a new option appearing on my Django Admin, which is not the case.
I have two separate apps Product and Tag which i used another app Product_tags to connect them together. in this way, if one of them don't exists, another one will work fine. inside Product_tags, I created a new TagProductSerializer which inherits ProductSerializer and I just added a new field named tag in fields list. product_tags/serializers.py:
class TagProductSerializer(ProductSerializer):
tags = serializers.PrimaryKeyRelatedField(queryset=Tag.objects.all())
class Meta:
model = Product
fields = [
'title',
'tags',
]
#...
and I did the same with Product viewsetproduct_tags/views.py
class TagProductViewset(ProductViewset):
serializer_class = SocialProductSerializer
and in my product_tags/urls.py I imported my Product router and i wanted to register my product viewset again for router. and there is my problem:
product/urls.py
router = routers.DefaultRouter()
router.register('product', ProductViewset)
urlpatterns = [
path('', include(router.urls)),
]
product_tags/urls.py (PROBLEM)
from product.urls import router
from .views import TagProductViewset
router.unregister('product') # I'm looking for something like this
router.register('product',TagProductViewset)
NOTE: I want to show the tags when getting product and because of that, I don't want to use different url for getting tag (e.g "api/product/tags/")
First Try:I tried to register the 'product' again (router.register('product',SocialProductViewset)) but it doesn't works
I fixed my problem but i forgot to say that here. I just created new router in my product_tags/urls.py and then I added it in urlpatterns inside of project/urls.py at top of my original product router.
product_tags/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import TagProductViewset
router = DefaultRouter()
router.register('product',TagProductViewset)
urlpatterns = [
path('', include(router.urls)),
]
project/urls.py
from django.urls import path, include
urlpatterns = [
path('api/v1/', include("product_tags.urls")),
path('api/v1/', include("product.urls")),
]
When I try a GET request on one of my API endpoints it can't find the endpoint.
urls.py file looks like this
from django.urls import path, include
from django.contrib import admin
from api.resources import NoteResource
note_resource = NoteResource()
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include(note_resource.urls)),
]
api.resources looks like this
from tastypie.resources import ModelResource
from api.models import Note
class NoteResource(ModelResource):
class Meta:
queryset = Note.objects.all()
resource_name = 'note'
Any idea why this is happening?
Solution: It appears that http://127.0.0.1:8000/api/note/ works properly.. why would this be?
You should also have one url entry in note_resource.urls for only /api request. Something similar to
path('api/', APIClass).
But, you never need that endpoint. Because, /api does not represent any actual request in your system.
I rather suggest to have following endpoints :
path('api/notes/',include(note_resource.urls))
in your main urls.py.
So that you can have multiple urls in main urls.py file representing each app.
path('api/<APP_NAME>(s)/',include(<APP_NAME>.urls))
And, you will manage other endpoints in your app urls.py file:
# Create a new Note
path(
'create/',
NoteCreate.as_view()
),
Is it possible to use generic url settings to implement the django rest interface for all models in django?
So instead of per model configuration:
class BlogResource(ModelResource):
model = Blog
urlpatterns = patterns('',
url(r'^Blog/$', ListOrCreateModelView.as_view(resource=BlogResource)),
url(r'^Blog/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=BlogResource)),
)
A more generic type of loading:
urlpatterns = patterns('',
url(r'^(?P<model>\w+)/$', GenericView.render_model_list()),
url(r'^(?P<model>\w+)/(?P<pk>[^/]+)/$', GenericView.render_model()),
)
With something that allows the system to generate the model and render it to the rest interface.
class BlogResource(ModelResource):
model = Blog
urlpatterns = patterns('',
url(r'^Blog/$', ListOrCreateModelView.as_view(resource=BlogResource)),
url(r'^Blog/(?P<pk>[^/]+)/$', InstanceModelView.as_view(resource=BlogResource)),
)
in more general way the solution would look like this (sorry - i wrote it by hand), but you still need to import these models and form the model tuple by hand.
from django.conf.urls.defaults import patterns, url
from models import Model1, Model2, Model3
urlconf = ['', ]
for obj in (Model1, Model2, Model3):
name = obj.__class__.__name__
ResourceClass = type('%sResource' % name, (obj,), {
'model': obj,
})
urlconf.append(url(r'^%s/$' % name, ListOrCreateModelView.as_view(resource=ResourceClass)))
urlconf.append(url(r'^%s/(?P<pk>[^/]+)/$' % name, ListOrCreateModelView.as_view(resource=ResourceClass)))
urlpatterns = patterns(urlconf)