I am new to Django and trying to work from django-jquery-file-upload
It has introduced me to class based Views.
I'm trying to replicate one of these class based views to contain a share link that will refer back to individual links in the gallery.
the class i'm trying to adapt is:
class PictureDeleteView(DeleteView):
model = Picture
def delete(self, request, *args, **kwargs):
"""
This does not actually delete the file, only the database record. But
that is easy to implement.
"""
self.object = self.get_object()
self.object.delete()
if request.is_ajax():
response = JSONResponse(True, {}, response_mimetype(self.request))
response['Content-Disposition'] = 'inline; filename=files.json'
return response
else:
return HttpResponseRedirect('/upload/new')
class JSONResponse(HttpResponse):
"""JSON response class."""
def __init__(self,obj='',json_opts={},mimetype="application/json",*args,**kwargs):
content = simplejson.dumps(obj,**json_opts)
super(JSONResponse,self).__init__(content,mimetype,*args,**kwargs)
This defaults to the template name picture_confirm_delete.html
How can i rewrite a class with the same functionality except that it points to a different template? or better still & in keeping with 'DRY' how can i reuse this class on another template?
I've had a look over the documentation here but can't seem to adapt it to my needs.
Thanks in advance.
In short, here's what's happening currently: a DeleteView in Django will render a default template based on the class name as explained in the docs:
The DeleteView page displayed to a GET request uses a template_name_suffix of '_confirm_delete'.
To change this, just overrule this behaviour by providing the template_name argument:
template_name
The full name of a template to use as defined by a string.
This works for any built-in class based view inheriting from a TemplateResponseMixin.
Apply it in the class definition or in the URL patterns (whichever you prefer):
In the class:
class MyOwnPictureDeleteView(PictureDeleteView):
template_name = "myown_picture_delete_template.html"
Yes, that's your complete new class based view.
or
In URLconf:
url(r'^picture_delete/(?P<pk>\d+)/', 'myapp.views.PictureDeleteView', \
{'template_name': 'myown_picture_delete_template.html'}),
Either way, you don't have to rewrite a single line of the original PictureDeleteView class, so this is as DRY as it gets.
Because DeleteView also inherits from SingleObjectTemplateResponseMixin it requires a template and therefore a template name.
But since you are not really using the functionality provided by Django's DeleteView but creating you own by using the HTTP method DELETE you can just change the parent class of your view from DeleteView to View.
Otherwise check which functionality from which of DeleteView's ancesctors you need, this is a good starting point for browsing class-based views (the official Django documentation isn't really yet).
Related
Using Django, I have a page with a form that is rendered using CreateView. The success_url is the same url as the original form. When the form is submitted and is valid, I want to insert some data into the context.
I'd like to know the most flexible and clear way to add this context data, because the example below (which works) feels sub-optimal for such a (presumably) common task. I'm looking for a more general solution that using SuccessMessageMixin.
class DemoView(CreateView):
template_name = "request-demo.html"
form_class = DemoForm
success_url = reverse_lazy("base:demo")
def form_valid(self, form):
create_and_send_confirmation_email(form)
return self.render_to_response(self.get_context_data(success_message=True))
Most generically you will define the succcess_url to be the DetailView of the object you have just created. In fact, if you do not define a success_url at all, the default get_success_url will return
url = self.object.get_absolute_url()
which is commonly the same thing.
If you want to do something special for a newly created object you might
Redirect to a different URL, such as a subclass of your object DetailView with a custom get_context_data method and/or template name. Or possibly, somewhere completely differerent.
Interrogate the newly created object (in the template and/or get_context_data method) to establish in some application-specific way that it is indeed newly created.
I have a function-based view that is currently working successfully. However, I want to learn how to create the equivalent Class Based View version of this function, using the generic UpdateView class -- though I imagine the solution, whatever it is, will be the exact same for CreateView, as well.
I know how to create and use Class Based Views generally, but there is one line of my function-based view that I have not been able to work into the corresponding UpdateView -- as usual with the Generic Editing Class Based Views, it's not immediately clear which method I need to override to insert the desired functionality.
The specific task that I can't port-over to the CBV, so to speak, is a line that overrides the queryset that will be used for the display of a specific field, one that is defined as ForeignKey to another model in my database.
First, the working function-based view, with highlight at the specific bit of code I can't get working in the CVB version:
#login_required
def update_details(request, pk):
"""update details of an existing record"""
umd_object = UserMovieDetail.objects.select_related('movie').get(pk=pk)
movie = umd_object.movie
if umd_object.user != request.user:
raise Http404
if request.method != 'POST':
form = UserMovieDetailForm(instance=umd_object)
# this is the single line of code I can't get working in UpdateView version:
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
else:
form = UserMovieDetailForm(instance=umd_object, data=request.POST)
if form.is_valid():
form.save()
return redirect(movie)
context = {'form': form, 'object': umd_object }
return render(request, 'movies/update_details.html', context)
I can recreate every part of this function-based view in UpdateView successfully except for this line (copied from above for clarity):
form.fields['user_guess'].queryset = User.objects.filter(related_game_rounds=movie.game_round)
What this line does: the default Form-field for a ForeignKey is ModelChoiceField, and it by default displays all objects of the related Model. My code above overrides that behavior, and says: I only want the form to display this filtered set of objects. It works fine, as is, so long as I'm using this function-based view.
Side-Note: I am aware that this result can be achieved by modifying the ModelForm itself in my forms.py file. The purpose of this question is to better understand how to work with the built-in Generic Class Based Views, enabling them to recreate the functionality I can already achieve with function-based views. So please, refrain from answering my question with "why don't you just do this in the form itself instead" -- I am already aware of this option, and it's not what I'm attempting to solve, specifically.
Now for the UpdateView (and again, I think it would be the same for CreateView). To start off, it would look essentially like this:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
model = UserMovieDetail
template_name = 'movies/update_details.html'
form_class = UserMovieDetailForm
login_url = 'login' # used by LoginRequiredMixin
# what method do I override here, to include that specific line of code, which needs
# to occur in the GET portion of the view?
def get_success_url(self):
return reverse('movies:movie', kwargs={'pk': self.object.movie.pk, 'slug': self.object.movie.slug })
The above is a working re-creation of my function-based view, replicating all the behavior except that one important line that filters the results of a specific field's ModelChoiceField display in the Form.
How do I get that line of code to function inside this UpdateView? I've reviewed the methods built-in to UpdateView on the classy class-based views website, and then attempted (by pure guess-work) to over-ride the get_form_class method, but I it didn't work, and I was basically shooting in the dark to begin with.
Note that since the functionality I want to re-create is about the display of items in ModelChoiceField of the form, the desired behavior applies to the GET portion of the view, rather than the POST. So I need to be able to override the form fields before the form is rendered for the first time, just like I did in my function based view. Where and how can I do this in UpdateView?
First, a note not related to form - from raise Http404 in functional view I understand that you want to allow user to access only his own movies. For that in class based view you can override get_queryset method:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
def get_queryset(self):
return UserMovieDetail.objects \
.filter(user=request.user) \
.select_related('movie')
Now let's move to customizing form.
Option 1 - .get_form()
You can override get_form method of the UpdateView:
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form(self, form_class=None):
form = super().get_form(form_class)
# add your customizations here
round = self.object.movie.game_round
form.fields['user_guess'].queryset = \
User.objects.filter(related_game_rounds=round)
return form
Option 2 - moving customizations to form class and .get_form_kwargs()
You might prefer to move customization logic from view to form. For that you can override form's __init__ method. If customization logic requires extra information (for example, queryset depends on current user), then you can also override get_form_kwargs method to pass extra parameters to the form:
# views.py
class UpdateDetailsView(LoginRequiredMixin, UpdateView):
form_class = UserMovieDetailForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs.update({'current_user': self.request.user})
return kwargs
# forms.py
class UserMovieDetailForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.current_user = kwargs.pop('current_user')
super().__init__(*args, **kwargs)
# add your customizations here
self.fields['user_guess'].queryset = ...
P.S. In general, great resource for understanding django class based views is https://ccbv.co.uk/
I use a variable in the base of my API url, identical to the setup found in the docs for Django REST Framework:
/api/<brand>/states/<state_pk>/
Everything after the base brand slug is a standard API format, and so I use ModelViewSets to generate all my list and detail views for my objects. Everything in the API is filtered by the brand, so this setup makes sense.
simplified project/urls.py
urlpatterns = patterns(
'',
url(r'^v2/(?P<brand_slug>\w+)/', include(router.urls, namespace='v2')),
)
simplified api/urls.py
router = routers.DefaultRouter()
router.register(r'states', StateViewSet)
router.register(r'cities', CityViewSet)
I also need hypermedia links for all models, and this is where I've run into problems. The REST framework doesn't know how to grab this brand variable and use it to generate correct links. Attempting to solve this problem by following the docs leaves me with 2 setbacks:
While the docs explain how to overwrite the HyperlinkRelatedField class, they never say where to put THAT class so that it works with my Serializers.
There's no mention on how to actually get the brand variable from the URL into the HyperlinkRelatedField class.
What are the missing elements here?
So, I figured it out.
Getting the URL variable into the Serializer
To do this, you need to overwrite the get_serializer_context() method for your ModelViewSet, and send in the variable from your kwargs
class BrandedViewSet(viewsets.ModelViewSet):
def get_serializer_context(self):
context = super().get_serializer_context()
context['brand_slug'] = self.kwargs.get('brand_slug')
return context
Then, you can just extend all of your ModelViewSets with that class:
class StateViewSet(BrandedViewSet):
queryset = State.objects.all()
serializer_class = StateSerializer
What's nice is that even though you've injected the Serializer with this variable, it's ALSO accessible from the HyperlinkedRelatedField class, via self.context, and that's how the next part is possible.
Building a Custom Hypermedia link with extra URL variables
The docs were correct in overwriting get_url():
class BrandedHyperlinkMixin(object):
def get_url(self, obj, view_name, request, format):
""" Extract brand from url
"""
if hasattr(obj, 'pk') and obj.pk is None:
return None
lookup_value = getattr(obj, self.lookup_field)
kwargs = {self.lookup_url_kwarg: lookup_value}
kwargs['brand_slug'] = self.context['brand_slug']
return reverse(
view_name, kwargs=kwargs, request=request, format=format)
Except, you'll notice I'm grabbing the variable from the context I set in part 1. I was unable to get the context from the object as the docs suggested, and this method turned out to be simpler.
The reason it's a mixin is because we need to extend TWO classes for this to work on all the url hyperlinks and not just the related field hyperlinks.
class BrandedHyperlinkedIdentityField(BrandedHyperlinkMixin,
serializers.HyperlinkedIdentityField):
pass
class BrandedHyperlinkedRelatedField(BrandedHyperlinkMixin,
serializers.HyperlinkedRelatedField):
pass
class BrandedSerializer(serializers.HyperlinkedModelSerializer):
serializer_related_field = BrandedHyperlinkedRelatedField
serializer_url_field = BrandedHyperlinkedIdentityField
Now we can safely extend our serializer and the hyperlinks show the brand variable!
class StateSerializer(BrandedSerializer):
class Meta:
model = State
fields = ('url', 'slug', 'name', 'abbrev', )
I have a subclass of DetailView to show details on a single object (obv).
I also have a subclass of ListView which provides the homepage (with some info on all projects)
But now I want to include a sidebar in my base template so that all pages will have links to each view provided by DetailView.
How can I do this in a way such that I can access all objects in the template provided by DetailView ?
class YourDetailView(DetailView):
# ...
def get_context_data(self, **kwargs):
context = super(YourDetailView, self).get_context_data(**kwargs)
context['all_objects'] = YourModel.objects.all()
return context
https://docs.djangoproject.com/en/dev/ref/class-based-views/mixins-single-object/#django.views.generic.detail.SingleObjectMixin.get_context_data
A couple of DRY methods:
Have all your DetailViews inherit from a base DetailView that uses get_context_data to retrieve the objects and adds them to the context
Write a custom template tag and use it in your base template
I am trying to use generic CreateView class to handle forms for a set of models inherited from the same base class.
class BaseContent(models.Model):
...
class XContent(BaseContent):
...
class YContent(BaseContent):
...
To keep things DRY, I want to define one CreateView class that will handle all inherited classes from BaseContent.
The url pattern for that view is:
url(r'^content/add/(?P<model_name>\w+)/$', ContentCreateView.as_view(), name='content_add')
Something like this should work:
class ContentCreateView(CreateView):
template_name = 'content_form.html'
def get_model(self, request):
# 'content' is the name of the application; model_name is 'xcontent', 'ycontent', ...
return ContentType.objects.get_by_natural_key('content', self.model_name)
But I am getting this exception:
ContentCreateView is missing a queryset. Define ContentCreateView.model, ContentCreateView.queryset, or override ContentCreateView.get_object().
This suggestion does not seem to hold as I am not willing to set a class attribute like model or queryset to keep the model form generated dynamic. Overriding the get_object does not seem relevant for creating an object.
I tried overriding get_queryset() but this method does not accept the request parameter, nor have access to self.model_name which comes from the url pattern.
Long story short, how can I make a CreateView use a dynamic form based on a parameter passed from the url?
Thanks.
You could set the model attribute from your your urls.py, depending on the url being called:
url(r'^content/add/x/$',
ContentCreateView.as_view(model=XContent), name='x_content_add'),
url(r'^content/add/y/$',
ContentCreateView.as_view(model=YContent), name='y_content_add')
I admit it's not perfect as you are repeating yourself a bit, but therefore you have the advantage of having different names for the same view, depending on the model! Besides that you could also do something similar with overriding form_class...
Had this problem for some time, but found the solution. You need to override the dispatch method, defined in as_view() (django.views.generic.base), something like this:
class ContentCreateView(CreateView):
def dispatch(self, request, *args, **kwargs):
for app in ['foo', 'bar']:
model = models.get_model(app, kwargs['modelname'])
if model:
self.model = model
break
return super(GenericEdit, self).dispatch(request, *args, **kwargs)
...
...