i want to subclass the dayarchiveview so i can render the data differently - django

i have a dayarchiveview in my django views.py file
class day_archive(DayArchiveView):
model=Timer
paginate_by=12
allow_future =True
allow_empty=True
month_format = '%m'
day_format='%d'
date_field='start_hour'
template_name='timer/timer_archive_date'
def get_queryset(self):
return Timer.objects.filter(author=self.request.user)
but i would like to render the data returned as a table using djangotables2 with something like this:
import django_tables2 as tables
class Job_table(tables.Table):
class Meta:
model = Timer
attrs = {"class": "paleblue"}
fields= ('start_hour','end_hour','category','subcategory','duration')
def render_duration(self,value):
from timehuman import sectohour
hh= sectohour(value)
return hh
how would i render my data as a table instead of the list rendered? ( context object_list by django) how do i access the data thats going to be sent to the object_list context and modify it?

This is what i ended up doing:
may be hackish, but i gives me access to the same data that's going to the view and i can get to render it as a table.
class day_archive(DayArchiveView):
model=Timer
paginate_by=12
allow_future =True
allow_empty=True
month_format = '%m'
day_format='%d'
date_field='begin_time'
template_name='timer/timer_archive_date'
def get_queryset(self):
return Timer.objects.filter(author=self.request.user)
def render_to_response(self, context, **response_kwargs):
"""
Returns a response, using the `response_class` for this
view, with a template rendered with the given context.
If any keyword arguments are provided, they will be
passed to the constructor of the response class.
"""
tbl = context['object_list'] #this line is my hack, i dont know better.
if (tb1 != None):
jt = Timer_table(blo)
RequestConfig(self.request).configure(jt)
from django.db.models import Sum
total = tbl.aggregate(Sum('duration'))
t2 = total['duration__sum']
if (t2 != None):
timedel= str(datetime.timedelta(seconds=float(t2)))
context['table']= jt
context['total'] = timedel
return self.response_class(
request = self.request,
template = self.get_template_names(),
context = context,
**response_kwargs
)

Related

Django REST, Accessing methods of the selected object

this part of my code fills the queryset with [category_object].subcats.all(). let subcats be a method of category object:
serializer:
class CatSrlz(serializers.ModelSerializer):
class Meta:
model = Category
fields = ('id', 'label', )
View:
class CatsViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Category.objects.filter(parent=None)
serializer_class = CatSrlz
def retrieve(self, request, *args, **kwargs):
# return Response({'res': self.kwargs})
queryset = Category.objects.get(pk=str(self.kwargs['pk'])).subCats.all()
dt = CatSrlz(queryset, many=True)
return Response(dt.data)
and url:
router.register(r'cats', views.CatsViewSet)
it works but i'm pretty sure that there must be a more correct way of doing so
Is there one?
thanks
When retrieving a single object, you can use the get_object method in your view, which look like this in DRF without modifications :
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
So you could adapt the part where you get your object :
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
and add your subcat logic there.
By the way, I don't get why you are using
dt = CatSrlz(queryset, many=True)
Shouldn't "retrieve" return a single object?

Django - Passing the model name to a view from url

How would I make it so I could pass the model name as a parameter in the url to a view? I would like to reuse this view and just pass the model name through to show a list of whatever model the parameter was.
Heres what I have so far
View
class ModelListView(ListView,objects):
model = objects
template_name = "model_list.html"
def get_context_data(self,**kwargs):
context = super(ModelListView, self).get_context_data(**kwargs)
context['listobjects'] = model.objects.all()
return context
URLS
url(r'^musicpack', MusicPackListView.as_view(), name='musicpack-list', objects = 'MusicPack'),
url(r'^instruments', MusicPackListView.as_view(), name='instrument-list', objects = 'Instrument'),
ANSWERED
Hey thanks for the answer
I've gone with the following and it seems to work.
View
class ModelListView(ListView):
template_name = "model_list.html"
def get_context_data(self,**kwargs):
context = super(ModelListView, self).get_context_data(**kwargs)
return context
URLS
#models
from inventory.views import MusicPack
from inventory.views import Instrument
#views
from inventory.views import ModelListView
url(r'^musicpacks', ModelListView.as_view(model = MusicPack,), name='musicpack-list'),
url(r'^instruments', ModelListView.as_view(model = Instrument,), name='instrument-list'),
I would pass perameter from urls to views like this:
VIEWS:
class ModelListView(ListView):
model = None
model_name= ''
object = None
template_name = "model_list.html"
def get_context_data(self,**kwargs):
context = super(ModelListView, self).get_context_data(**kwargs)
context['listobjects'] = model.objects.all()
return context
URLS:
url(r'^musicpack', ModelListView.as_view( model= MusicPackList,model_name= 'music_pack_list' object = 'MusicPack')),
url(r'^instruments', ModelListView.as_view( model=InstrumentPackList,model_name= 'instrument_pack_list', object= 'InstrumentPack'))

A Category model which creates proxy models for related model admin

So I'm having a bit of trouble with trying to create a model that will define dynamic proxy models that manage a related model in the admin site. I know that sentence was confusing, so I'll just share my code instead.
models.py
class Cateogry(models.Model):
name = models.CharField(...)
class Tag(models.Model):
name = models.CharField(...)
category = models.ForeignKey(Cateogry)
What I want to achieve is that in the admin site, instead of having one ModelAdmin for the Tag model, for each category I will have a modeladmin for all related tags. I have achieved this using this answer. Say I have a category named A:
def create_modeladmin(modeladmin, model, name = None):
class Meta:
proxy = True
app_label = model._meta.app_label
attrs = {'__module__': '', 'Meta': Meta}
newmodel = type(name, (model,), attrs)
admin.site.register(newmodel, modeladmin)
return modeladmin
class CatA(TagAdmin):
def queryset(self, request):
qs = super(CatA, self).queryset(request)
return qs.filter(cateogry = Cateogry.objects.filter(name='A'))
create_modeladmin(CatA, name='CategoryAtags', model=Tag)
But this is not good enough, because obviously I still need to manually subclass the TagAdmin model and then run create_modeladmin. What I need to do, is loop over all Category objects, for each one create a dynamic subclass for Tagadmin (named after the category), then create a dynamic proxy model from that, and this is where my head starts spinning.
for cat in Category.objects.all():
NewSubClass = #somehow create subclass of TagAdmin, the name should be '<cat.name>Admin' instead of NewSubClass
create_modeladmin(NewSubClass, name=cat.name, model=Tag)
Any guidance or help would be much appreciated
Dynamic ModelAdmins don't work well together with the way admin registeres models.
I suggest to create subviews in the CategoryAdmin.
from django.conf.urls import patterns, url
from django.contrib import admin
from django.contrib.admin.options import csrf_protect_m
from django.contrib.admin.util import unquote
from django.core.urlresolvers import reverse
from demo_project.demo.models import Category, Tag
class TagAdmin(admin.ModelAdmin):
# as long as the CategoryTagAdmin class has no custom change_list template
# there needs to be a default admin for Tags
pass
admin.site.register(Tag, TagAdmin)
class CategoryTagAdmin(admin.ModelAdmin):
""" A ModelAdmin invoked by a CategoryAdmin"""
read_only_fields = ('category',)
def __init__(self, model, admin_site, category_admin, category_id):
self.model = model
self.admin_site = admin_site
self.category_admin = category_admin
self.category_id = category_id
super(CategoryTagAdmin, self).__init__(model, admin_site)
def queryset(self, request):
return super(CategoryTagAdmin, self).queryset(request).filter(category=self.category_id)
class CategoryAdmin(admin.ModelAdmin):
list_display = ('name', 'tag_changelist_link')
def tag_changelist_link(self, obj):
info = self.model._meta.app_label, self.model._meta.module_name
return '<a href="%s" >Tags</a>' % reverse('admin:%s_%s_taglist' % info, args=(obj.id,))
tag_changelist_link.allow_tags = True
tag_changelist_link.short_description = 'Tags'
#csrf_protect_m
def tag_changelist(self, request, *args, **kwargs):
obj_id = unquote(args[0])
info = self.model._meta.app_label, self.model._meta.module_name
category = self.get_object(request, obj_id)
tag_admin = CategoryTagAdmin(Tag, self.admin_site, self, category_id=obj_id )
extra_context = {
'parent': {
'has_change_permission': self.has_change_permission(request, obj_id),
'opts': self.model._meta,
'object': category,
},
}
return tag_admin.changelist_view(request, extra_context)
def get_urls(self):
info = self.model._meta.app_label, self.model._meta.module_name
urls= patterns('',
url(r'^(.+)/tags/$', self.admin_site.admin_view(self.tag_changelist), name='%s_%s_taglist' % info )
)
return urls + super(CategoryAdmin, self).get_urls()
admin.site.register(Category, CategoryAdmin)
The items in the categories changelist have an extra column with a link made by the tag_changelist_link pointing to the CategoryAdmin.tag_changelist. This method creates a CategoryTagAdmin instance with some extras and returns its changelist_view.
This way you have a filtered tag changelist on every category. To fix the breadcrumbs of the tag_changelist view you need to set the CategoryTagAdmin.change_list_template to a own template that {% extends 'admin/change_list.html' %} and overwrites the {% block breadcrumbs %}. That is where you will need the parent variable from the extra_context to create the correct urls.
If you plan to implement a tag_changeview and tag_addview method you need to make sure that the links rendered in variouse admin templates point to the right url (e.g. calling the change_view with a form_url as paramter).
A save_model method on the CategoryTagAdmin can set the default category when adding new tags.
def save_model(self, request, obj, form, change):
obj.category_id = self.category_id
super(CategoryTagAdmin, self).__init__(request, obj, form, change)
If you still want to stick to the apache restart aproach ... Yes you can restart Django. It depends on how you are deploying the instance.
On an apache you can touch the wsgi file that will reload the instance os.utime(path/to/wsgi.py.
When using uwsgi you can use uwsgi.reload().
You can check the source code of Rosetta how they are restarting the instance after the save translations (views.py).
So I found a half-solution.
def create_subclass(baseclass, name):
class Meta:
app_label = 'fun'
attrs = {'__module__': '', 'Meta': Meta, 'cat': name }
newsub = type(name, (baseclass,), attrs)
return newsub
class TagAdmin(admin.ModelAdmin):
list_display = ('name', 'category')
def get_queryset(self, request):
return Tag.objects.filter(category = Category.objects.filter(name=self.cat))
for cat in Category.objects.all():
newsub = create_subclass(TagAdmin, str(cat.name))
create_modeladmin(newsub, model=Tag, name=str(cat.name))
It's working. But every time you add a new category, you need to refresh the server before it shows up (because admin.py is evaluated at runtime). Does anyone know a decent solution to this?

Adding root element to json response (django-rest-framework)

I am trying to determine the best way to add a root element to all json responses using django and django-rest-framework.
I think adding a custom renderer is the best way to accomplish what I want to achieve and this is what I have come up with so far:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
#override the render method
def render(self, data, accepted_media_type=None, renderer_context=None):
#call super, as we really just want to mess with the data returned
json_str = super(CustomJSONRenderer, self).render(data, accepted_media_type, renderer_context)
root_element = 'contact'
#wrap the json string in the desired root element
ret = '{%s: %s}' % (root_element, json_str)
return ret
The tricky part now is dynamically setting the root_element based on the view that render() is being called from.
Any pointers/advice would be greatly appreciated,
Cheers
For posterity, below is the final solution. It has grown slightly from the original as it now reformats paginated results as well.
Also I should have specified before, that the reason for the JSON root element is for integration with an Ember front end solution.
serializer:
from rest_framework.serializers import ModelSerializer
from api.models import Contact
class ContactSerializer(ModelSerializer):
class Meta:
model = Contact
#define the resource we wish to use for the root element of the response
resource_name = 'contact'
fields = ('id', 'first_name', 'last_name', 'phone_number', 'company')
renderer:
from rest_framework.renderers import JSONRenderer
class CustomJSONRenderer(JSONRenderer):
"""
Override the render method of the django rest framework JSONRenderer to allow the following:
* adding a resource_name root element to all GET requests formatted with JSON
* reformatting paginated results to the following structure {meta: {}, resource_name: [{},{}]}
NB: This solution requires a custom pagination serializer and an attribute of 'resource_name'
defined in the serializer
"""
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
#determine the resource name for this request - default to objects if not defined
resource = getattr(renderer_context.get('view').get_serializer().Meta, 'resource_name', 'objects')
#check if the results have been paginated
if data.get('paginated_results'):
#add the resource key and copy the results
response_data['meta'] = data.get('meta')
response_data[resource] = data.get('paginated_results')
else:
response_data[resource] = data
#call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
pagination:
from rest_framework import pagination, serializers
class CustomMetaSerializer(serializers.Serializer):
next_page = pagination.NextPageField(source='*')
prev_page = pagination.PreviousPageField(source='*')
record_count = serializers.Field(source='paginator.count')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
# Takes the page object as the source
meta = CustomMetaSerializer(source='*')
results_field = 'paginated_results'
Credit to ever.wakeful for getting me 95% of the way there.
Personally, I wanted to add meta data to every api request for a certain object, regardless of whether or not it was paginated. I also wanted to simply pass in a dict object that I defined manually.
Tweaked Custom Renderer
class CustomJSONRenderer(renderers.JSONRenderer):
def render(self, data, accepted_media_type=None, renderer_context=None):
response_data = {}
# Name the object list
object_list = 'results'
try:
meta_dict = getattr(renderer_context.get('view').get_serializer().Meta, 'meta_dict')
except:
meta_dict = dict()
try:
data.get('paginated_results')
response_data['meta'] = data['meta']
response_data[object_list] = data['results']
except:
response_data[object_list] = data
response_data['meta'] = dict()
# Add custom meta data
response_data['meta'].update(meta_dict)
# Call super to render the response
response = super(CustomJSONRenderer, self).render(response_data, accepted_media_type, renderer_context)
return response
Parent Serializer and View Example
class MovieListSerializer(serializers.ModelSerializer):
class Meta:
model = Movie
meta_dict = dict()
meta_dict['foo'] = 'bar'
class MovieViewSet(generics.ListAPIView):
queryset = Movie.objects.exclude(image__exact = "")
serializer_class = MovieListSerializer
permission_classes = (IsAdminOrReadOnly,)
renderer_classes = (CustomJSONRenderer,)
pagination_serializer_class = CustomPaginationSerializer
paginate_by = 10

Import csv data into database in Django Admin

I've tried to import a csv file into a database by tweaking the modelform inside the admin doing this:
models.py:
class Data(models.Model):
place = models.ForeignKey(Places)
time = models.DateTimeField()
data_1 = models.DecimalField(max_digits=3, decimal_places=1)
data_2 = models.DecimalField(max_digits=3, decimal_places=1)
data_3 = models.DecimalField(max_digits=4, decimal_places=1)
Forms.py:
import csv
class DataImport(ModelForm):
file_to_import = forms.FileField()
class Meta:
model = Data
fields = ("file_to_import", "place")
def save(self, commit=False, *args, **kwargs):
form_input = DataImport()
self.place = self.cleaned_data['place']
file_csv = request.FILES['file_to_import']
datafile = open(file_csv, 'rb')
records = csv.reader(datafile)
for line in records:
self.time = line[1]
self.data_1 = line[2]
self.data_2 = line[3]
self.data_3 = line[4]
form_input.save()
datafile.close()
Admin.py:
class DataAdmin(admin.ModelAdmin):
list_display = ("place", "time")
form = DataImport
admin.site.register(Data, DataAdmin)
But i'm stuck trying to import the file i put in "file_to_import" field. Getting AttributeError in forms.py : 'function' object has no attribute 'FILES'.
What i'm doing wrong?
After a long search i found an answer: Create a view inside the admin using a standard form
Form:
class DataInput(forms.Form):
file = forms.FileField()
place = forms.ModelChoiceField(queryset=Place.objects.all())
def save(self):
records = csv.reader(self.cleaned_data["file"])
for line in records:
input_data = Data()
input_data.place = self.cleaned_data["place"]
input_data.time = datetime.strptime(line[1], "%m/%d/%y %H:%M:%S")
input_data.data_1 = line[2]
input_data.data_2 = line[3]
input_data.data_3 = line[4]
input_data.save()
The view:
#staff_member_required
def import(request):
if request.method == "POST":
form = DataInput(request.POST, request.FILES)
if form.is_valid():
form.save()
success = True
context = {"form": form, "success": success}
return render_to_response("imported.html", context,
context_instance=RequestContext(request))
else:
form = DataInput()
context = {"form": form}
return render_to_response("imported.html", context,
context_instance=RequestContext(request))
The rest is part of this post:
http://web.archive.org/web/20100605043304/http://www.beardygeek.com/2010/03/adding-views-to-the-django-admin/
Take a look at django-admin-import, it does more or less exactly what you want -- you can upload a XLS (not a CSV, but that should not matter) and lets you assign columns to model fields. Default values are also supported.
https://pypi.org/project/django-admin-import/
Additionally, it does not take away the possibility to modify individual records by hand because you don't have to replace the default model form used in the administration.
In the save() method, you don't have any access to the request object - you can see that it's not passed in. Normally you would expect to have a NameError there, but I suspect that you've got a function elsewhere in the file called request().
At the point of saving, all the relevant data should be in cleaned_data: so you should be able to do
file_csv = self.cleaned_data['file_to_import']
At that point you'll have another problem, which is when you get to open - you can't do that, as file_to_import is not a file on the server filesystem, it's an in-memory file that has been streamed from the client. You should be able to pass file_csv directly to csv.reader.