In the last few weeks I've switched from developing an application that processes a simple XML file, then writes its contents to an Oracle DB (cx_Oracle) and outputs in HTML, to using a Django framework. The Django framework switch wasn't necessary, but since I have an opportunity to develop something by using Django, I thought why not as it is a new area for me and can't damage my CV.
Anyway, I'm having issues with knowing what to write in my urls.py file when importing a Class from the views.py file. Here are the current contents:
urls.py
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.abc),
)
views.py
class Pymat_program:
def abc(self, request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
I've tried various permutations of using request, not using request, and also how the class is called in the url tuple, all to no avail. When I use a definition outside of the class (i.e. not in any class), then this is correctly displayed in HTML.
You don't want to wrap your program in a class. (In general, in Python you should treat modules like, say, Java might treat classes with static members only.)
There are two approaches, really:
Function-based views
urls.py
from myproj.views import abc_view
urlpatterns = (
url(r'^pymat/$', abc_view),
)
views.py
def abc_view(request):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Class-based views
urls.py
from myproj.views import AbcView
urlpatterns = (
url(r'^pymat/$', AbcView.as_view()),
)
views.py
from django.views.generic import View
class AbcView(View):
def get(self, request, *args, **kwargs):
test = "<html><body>Random text.</body></html>"
return HttpResponse(test)
Since you are using class based views in Django, so you have to call this class in URL like:
from myproj.views import Pymat_program
pymatProgram = Pymat_program()
urlpatterns = (
url(r'^pymat/$', pymatProgram.as_view()),
)
and then use get or post methods name in class like:
class Pymat_program:
model = ModelName
def post(self, request, *args, **kwargs):
....
2nd way is to use function based views:
urls.py
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^articles/2003/$', views.view_name),
]
views.py
from django.http import HttpResponse
import datetime
def view_name(request):
now = datetime.datetime.now()
html = "<html><body>It is now %s.</body></html>" % now
return HttpResponse(html)
for more details look into class based views docs
Related
I have a view for creating blog posts which redirects the user to a form to fill the blog post content like this:
from django.views import View
from app.models import BlogPost
class CreateBlogPost(View):
def post():
new_post = BlogPost.objects.create()
return redirect(reverse('post_edit'), args=[new_post.id])
class EditBlogPost(View):
...
And my urls are the following:
from django.urls import path
from app.views import CreateBlogPost
urlpatterns = [
path('post', CreateBlogPost.as_view(), name='post_create'),
path('post/<int:pk>/edit', EditBlogPost.as_view(), name='post_edit')
]
I would like to: test the CreateBlogPost by asserting that it redirects to post/<some_id>/edit.
I've tried: using the method assertRedirects from django's SimpleTestCase, but couldn't find a way to make this work.
I'm also using pytest-django, in case that's relevant.
I'am new to Python programming and Django Framework.
I have this app named Devices with multiple tables related with each other (Brand, Version, Location etc) and has crud. What I would like to do is to reuse my functions(list,create,detail etc.) instead of creating individual functions for each table.
Right now I'am unable to change BrandForm dynamically to example VersionForm. Is this possible? and am I doing the right thing?
urls.py
from django.urls import path
from .views import *
app_name = 'devices'
urlpatterns = [
# BRAND
path('brand/create', create_view, name='brand-create'),
# VERSION
path('version/create', create_view, name='version-create'),
]
views.py
from .models import *
from .forms import BrandForm, VersionForm
# GET PAGE NAME TO GET MODEL NAME
def get_page_name(request):
currentUrl = request.get_full_path()
page = currentUrl.split('/')[-2].split('.')[0] # Example /devices/brand/list; Get 2nd URL
return page
def create_view(request):
page = get_page_name(request) # GET PAGE MODEL NAME
page_title = 'Create ' + page.capitalize()
model = apps.get_model('devices', page.capitalize())
status = "created"
if request.method == 'POST':
form = BrandForm(request.POST)
else:
form = BrandForm()
return save_brand_form(request, form, 'devices/brand/create.html', page_title, status)
While it's possible to fix your solution, it will be non-standard, very hard to read and probably full of bugs. Luckily, django provide very good solution to implement CRUD - class-based views.
So, in your case:
urls.py
from .views import BrandCreateView, VersionCreateView
urlpatterns = [
path('brand/create', BrandCreateView.as_view()),
path('version/create', VersionCreateView.as_view()),
]
views.py
from django.views.generic.edit import CreateView
from .models import Brand, Version
from .forms import BrandForm, VersionForm
class BrandCreateView(CreateView):
model = Brand
form_class = BrandForm
template_name = 'devices/brand/create.html'
class VersionCreateView(CreateView):
model = Version
form_class = VersionForm
template_name = 'devices/version/create.html'
I have defined a custom action for a ViewSet
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
And the viewset is registered to url in the conventional way
from django.conf.urls import url, include
from rest_framework import routers
from api import views
router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet, base_name='myuser')
urlpatterns = [
url(r'^', include(router.urls)),
]
The URL /api/users/gender/ works. But I don't know how to get it using reverse in unit test. (I can surely hard code this URL, but it'll be nice to get it from code)
According to the django documentation, the following code should work
reverse('admin:app_list', kwargs={'app_label': 'auth'})
# '/admin/auth/'
But I tried the following and they don't work
reverse('myuser-list', kwargs={'app_label':'gender'})
# errors out
reverse('myuser-list', args=('gender',))
# '/api/users.gender'
In the django-restframework documentation, there is a function called reverse_action. However, my attempts didn't work
from api.views import UserViewSet
a = UserViewSet()
a.reverse_action('gender') # error out
from django.http import HttpRequest
req = HttpRequest()
req.method = 'GET'
a.reverse_action('gender', request=req) # still error out
What is the proper way to reverse the URL of that action?
You can use reverse just add to viewset's basename action:
reverse('myuser-gender')
See related part of docs.
You can print all reversible url names of a given router at startup with the get_urls() method
In urls.py after the last item is registered to the router = DefaultRouter() variable, you can add:
#router = routers.DefaultRouter()
#router.register(r'users', UserViewSet)
#...
import pprint
pprint.pprint(router.get_urls())
The next time your app is initialized it will print to stdout something like
[<RegexURLPattern user-list ^users/$>,
<RegexURLPattern user-list ^users\.(?P<format>[a-z0-9]+)/?$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)/$>,
<RegexURLPattern user-detail ^users/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$>,
#...
]
where 'user-list' and 'user-detail' are the names that can be given to reverse(...)
Based on DRF Doc.
from rest_framework import routers
router = routers.DefaultRouter()
view = UserViewSet()
view.basename = router.get_default_basename(UserViewSet)
view.request = None
or you can set request if you want.
view.request = req
In the end, you can get a reverse action URL and use it.
url = view.reverse_action('gender', args=[])
If you want to use UserViewset().reverse_action() specifically, you can do this by assigning a basename and request = None to your ViewSet:
from rest_framework import viewsets
class UserViewSet(viewsets.ModelViewSet):
basename = 'user'
request = None
#action(methods=['get'], detail=False, permission_classes=[permissions.AllowAny])
def gender(self, request):
....
and in urls.py:
router.register('user', UserViewSet, basename=UserViewSet.basename)
Then calling url = UserViewset().reverse_action('gender') or url = UserViewset().reverse_action(UserViewSet().gender.url_name) will return the correct url.
Edit:
Above method only works when calling reverse_action() only once because the ViewSetMixin.as_view() method overrides basename on instantiation. This can be solved by adding a custom subclass of GenericViewSet (or ModelViewSet if preferred) like so:
from django.utils.decorators import classonlymethod
from rest_framework.viewsets import GenericViewSet
class ReversibleViewSet(GenericViewSet):
basename = None
request = None
#classonlymethod
def as_view(cls, actions=None, **initkwargs):
basename = cls.basename
view = super().as_view(actions, **initkwargs)
cls.basename = basename
return view
and subclassing this class for the specific ViewSets, setting basename as desired.
The answer is reverse('myuser-gender').
Note! But remember that DRF will replace _ in the action name with -. That means if the action name is my_pretty_action in the reverse you should use reverse(app-my-pretty-action).
According to your code you would like to get the list of users you use
reverse("myuser-list")
details
reverse("myuser-detail",kwargs={"id":1})
In here you might notice i use list or detail by adding it to the reverse so if you add appname you can just input it thats because we are using viewsets
I'm using Django-rest-framework==3.3.2 and Django==1.8.8. I have a simple GenericView
from rest_framework import generics
from rest_framework.decorators import detail_route
class MyApiView(generics.RetrieveAPIView):
serializer = MySerializer
def get(self, *args, **kwargs):
super(MyApiView, self).get(*args, **kwargs)
#detail_route(methods=['post'])
def custom_action(self, request)
# do something important
return Response()
This works fine if I use the router that django-rest-framework offers, however I'm creating all my urls manually and would like to do the same with the detail_route.
I wonder if it's possible for me to do something like this:
from django.conf.urls import patterns, url
from myapi import views
urlpatterns = patterns(
'',
url(r'^my-api/$', views.MyApiView.as_view()),
url(r'^my-api/action$', views.MyApiView.custom_action.as_view()),
)
Of course this second url doesn't work. It's just an example of what I would like to do.
Thanks in advance.
As per the example from the Viewsets docs, you can extract individual methods into views:
custom_action_view = views.MyApiView.as_view({"post": "custom_action"})
You're then free to route this as normal:
urlpatterns = [
url(r'^my-api/action$', custom_action_view),
]
I hope that helps.
I have a good many class based views that use reverse(name, args) to find urls and pass this to templates. However, the problem is class based views must be instantiated before urlpatterns can be defined. This means the class is instantiated while urlpatterns is empty leading to reverse throwing errors. I've been working around this by passing lambda: reverse(name, args) to my templates but surely there is a better solution.
As a simple example the following fails with exception:
ImproperlyConfigured at xxxx
The included urlconf mysite.urls doesn't have any patterns in it
mysite.urls
from mysite.views import MyClassView
urlpatterns = patterns('',
url(r'^$' MyClassView.as_view(), name='home')
)
views.py
class MyClassView(View):
def get(self, request):
home_url = reverse('home')
return render(request, 'home.html', {'home_url':home_url})
home.html
<p><a href={{ home_url }}>Home</a></p>
I'm currently working around the problem by forcing reverse to run on template rendering by changing views.py to
class MyClassView(View):
def get(self, request):
home_url = lambda: reverse('home')
return render(request, 'home.html', {'home_url':home_url})
and it works, but this is really ugly and surely there is a better way. So is there a way to use reverse in class based views but avoid the cyclic dependency of urlpatterns requiring view requiring reverse requiring urlpatterns...
EDIT:
I'm using this as so:
views.py
def sidebar_activeusers(cls):
sidebar_dict = {'items' = []}
qs = models.random.filter.on.users
for user in qs:
item = {
'value': user.name,
'href': reverse('user_profile', args=[hash_id(user.id)])}
sidebar = loader.get_template('sidebar.html')
cls.base_dict['sidebar_html'] = sidebar.render(Context(sidebar_dict))
return cls
#sidebar_activeusers
class MyView1(View):
base_dict = {}
...
#other_sidebar_that_uses_same_sidebar_template
class MyView2(View):
basically I'd like to use the same sidebar template for a few different content types. Although the models displayed in the sidebar will be arbitrary the format will always be the same.
For your example MyClassView, it's ok to use reverse
class MyClassView(View):
def get(self, request):
home_url = reverse_lazy('home')
return render(request, 'home.html', {'home_url': home_url},
The reverse method is not called when the class is defined -- It is only called when the request is processed and the get method is called, so there shouldn't be an error. I have tested the example above and it works fine.
However, When you use your sidebar_activeusers decorator, the reverse call occurs before the url conf is loaded. In cases like these, you can use reverse_lazy. Here's an example of reverse_lazy in action:
from django.core.urlresolvers import reverse_lazy
class MyOtherClassView(View):
home_url = reverse_lazy('home')
def get(self, request):
return render(request, 'home.html', {'home_url':self.home_url})
This time, home_url is set when the class is defined, so reverse_lazy is required instead of reverse, because the url conf hasn't loaded yet.
Your home url can be accessed directly in your template by using declaring {% url 'home' %}. It's a standard way to access your named urls in your urls.py file.
You do not have to send the home_url variable to your template explicitly in your class-based view function.
In other words, in your home.html file:-
<p>Home</p>
will do.