errors when using tastypie resource in view - django

here is my resource:
class ImageResource(ModelResource):
album = fields.ForeignKey(AlbumResource, 'album')
upload_by = fields.ForeignKey(UserResource, 'upload_by')
class Meta:
always_return_data=True
filtering = {
"album": ('exact',),
}
queryset = Image.objects.all()
cache = SimpleCache(timeout=100)
resource_name = 'image'
authorization = ImageAuthorization()
class ImageAuthorization(Authorization):
def read_list(self, object_list, bundle):
# This assumes a ``QuerySet`` from ``ModelResource``.
userprofile = UserProfile.objects.get(user = bundle.request.user)
album = Album.objects.filter(family=userprofile.family)
return object_list.filter(album__in=album)
and when I try to use ImageResource in view like:
#csrf_exempt
def upload(request):
if request.method == 'POST':
if request.FILES:
uploadedfile = request.FILES
file = uploadedfile['item']
album = Album.objects.get(pk=int(request.POST['album_id']))
img = Image(
album = album,
name = file.name,
src=file,
upload_by = request.user,
)
# img.save()
ir = ImageResource()
uploaded_img = ir.obj_get(src=file)
print uploaded_img
return HttpResponse('True')
this will always rasie an error says
obj_get() takes exactly 2 arguments (1 given)
what's wrong with my code??? and how can I get the just uploaded image's resouce

Why are you trying to create instances of ImageResource? That makes no sense.
obj_get is a method for a tastypie resource, which is part of the resource flow chart. It expects a bundle object.
obj_get(self, bundle, **kwargs): ...
You do not have to create a resource on the fly for every image you upload, you don't even need to instantiate one, as the url module does this for you.
I recommend you re-read the documentation and register an ImageResource and/or AlbumResource accordingly. Those resources will pickup uploaded images or albums automatically after you register the resources to your urls module.

Related

How to pass created object in createview right to updateview in django

I have a createview view in my django app:
### Create a Group
class GroupCreateView(CreateView): # {{{
model = Group
form_class = GroupForm
template_name = 'ipaswdb/group/group_form.html'
success_url = '/ipaswdb/group/'
def get_context_data(self, **kwargs):
..do stuff..
def post(self, request, *args, **kwargs):
if self.request.POST.has_key('submit'):
form = GroupForm(request.POST)
if form.is_valid():
### Save the group
self.object = form.save()
#### Adding a provider forces a default location
#if form['default_location'].value() == True:
### We are forcing the creation of a GroupLocation when a new Group is created
gl = GroupLocation(
group = Group.objects.get(pk=self.object.id),
doing_business_as = self.object.group_name,
default_group_location = True,
mailing_address_line_one = self.object.mailing_address_line_one,
mailing_address_line_two = "",
mailing_city = self.object.mailing_city,
mailing_state = self.object.mailing_state,
mailing_zip_code = self.object.mailing_zip_code,
mailing_phone = self.object.mailing_phone,
mailing_fax = self.object.mailing_fax,
contact = self.object.group_contact,
physical_address_line_one = self.object.billing_address_line_one,
physical_address_line_two = "",
physical_city = self.object.billing_city,
physical_state = self.object.billing_state,
physical_zip_code = self.object.billing_zip_code,
physical_phone = self.object.billing_phone,
physical_fax = self.object.billing_fax,
)
gl.save()
new_grploc = gl
self.object.default_location_id = new_grploc.id
self.object.save()
new_group_id = self.object.id
new_grploc_id = new_grploc.id
### Now check the list of providers to see what has changed
print "group_add_provider: ",
print request.POST.getlist('group_add_provider')
add_providers = request.POST.getlist('group_add_provider')
if add_providers:
for pro in add_providers:
add_grploc = GroupLocationProvider(
grouplocation=GroupLocation.objects.get(pk=new_grploc_id),
provider=Provider.objects.get(pk=pro)
)
add_grploc.save()
### Now check the list of insurances to see what has changed
print "group_add_insurance: ",
print request.POST.getlist('group_add_insurance')
add_insurances = request.POST.getlist('group_add_insurance')
if add_insurances:
for ins in add_insurances:
add_grpins = GroupInsurance(
group=Group.objects.get(pk=new_group_id),
insurance=Insurance.objects.get(pk=ins)
)
add_grpins.save()
#return HttpResponseRedirect(self.get_success_url()) #how it used to work, just fine but would go back to my list of groups
return HttpResponseRedirect('ipaswdb:group_detail', self.object.pk) #want it to call my edit view here.
My Url Patterns
app_name = 'ipaswdb'
urlpatterns = [
url(r'^group/(?P<pk>[0-9]+)/$', GroupUpdateView.as_view(), name='group_detail'),
url(r'^group/add/$', GroupCreateView.as_view(), name='group_add'),
..etc..
Got an error but I feel I am closer?
DisallowedRedirect at /ipaswdb/group/add/
Unsafe redirect to URL with protocol 'ipaswdb'
I really want to load the page with the created object but as an updateview
Anyway to do this from the create view?
It is highly recommended to return a redirect request from a successful POST request. Otherwise a user might accidentally create multiple objects by reloading the page. Something like this:
from django.shortcuts import redirect
...
return redirect('name-of-update-url', pk=obj.pk)
If you really do not want to use a redirect, it is a bit more involved. Class based views are not meant to be called directly. The as_view method you use in your urls.py creates a wrapper function which instantiates the class and calls dispatch, which selects the right handler method (get/post/...). But you can't use as_view, because you have a POST request, but probably want to call the get method.
So you have to create an instance of your UpdateView and directly call its get method. With a standard UpdateView, can try something like this:
class GroupCreateView(CreateView):
...
def post(self, request, *args, **kwargs):
...
obj = ... # create your object
update_view = UpdateView()
update_view.request = self.request
update_view.args = []
update_view.kwargs = {'pk': obj.pk}
return update_view.get(self.request)
If you heavily customized your UpdateView, you might have to adapt this.
My go-to resource how Django's class-based views look under the hood is https://ccbv.co.uk

tastypie, GET/POST a field of a model?

I have a model like below.
class Checklist(models.Model):
name = models.CharField(max_length=50, default="mylist")
items = JSONField(default=get_default_checklist)
user = models.ForeignKey(User, related_name='checklists')
For a given Checklist.id, I want to get items field only, so I created a resource for it.
class ChecklistItemsResource(ModelResource):
def dehydrate_items(self, bundle):
return json.dumps(bundle.obj.items, ensure_ascii=False)
class Meta:
queryset = models.Checklist.objects.all()
resource_name = 'checklist_items'
fields = ['items']
and I get the data with url /api/v1/checklist_items/8/?format=json
id=8 is actually id of checklist not id of checklist.items.
edit -
I think /api/v1/checklist/8/items/ looks better than /api/v1/checklist_items/8/.
To represent items field of checklist(id=8).
How do you create resource/url to fetch/update a specific field of a model?
You could use the prepend_urls hook to create a /items/ subresource for your Checklist resource. Add the following to your resource:
from django.conf.urls import url
from tastypie.bundle import Bundle
from tastypie.utils import trailing_slash
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/(?P<%s>\w[\w/-]*)/items%s$" % (self._meta.resource_name, self._meta.detail_uri_name, trailing_slash()), self.wrap_view('get_items'), name="api_get_items"),
]
def get_items(self, request, **kwargs):
pk = kwargs[self._meta.detail_uri_name]
try:
checklist = Checklist.objects.get(pk=pk)
except Checklist.DoesNotExist:
return self.create_response(request, {}, status=404)
if request.method == 'GET':
bundle = Bundle(request=request, obj=checklist)
bundle.data['items'] = self._meta.fields['items'].dehydrate(bundle)
if hasattr(self, 'dehydrate_items'):
bundle.data['items'] = self.dehydrate_items(bundle)
return self.create_response(request, bundle)
elif request.method == 'PATCH':
data = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
checklist.items = data
checklist.save()
return self.create_response(request, {})
To update the items field, send a PATCH request to the /items/ endpoint with new serialized new value in the body. This view can be easily extended for general case.

Retrieving images from GridFS using django-tastypie-mongoengine

I have a project in Django, and I'm using mongoengine to save images into a Mongo database using GridFSStorage.
All ok so far, but the problem is... when trying to retrieve the images via http request, with a REST API made with django-tastypie-mongoengine, I get back a json object like this:
{"file": "<GridFSProxy: 516ed7cf56ba7d01eb09f522>", "id": "516ed7cf56ba7d01eb09f524", "resource_uri": "/api/v1/pic/516ed7cf56ba7d01eb09f524/"}
Does anybody know how could I get the file from GridFS via http request?
Many thanks!
You'll need to write your own view, but you can make it seem like it's part of the API. First, the view:
def api_image(pk):
obj = get_object_or_404(Model, pk=pk)
image_file = obj.file
return Response(image_file.read(),
mime_type='image/png') # or whatever the MIME type is
Then, you can map it in your urls.py:
url('^/api/v1/pic/(?P<pk>\w+)/file/$', api_image)
And to make sure tastypie shows what you want in the output:
def dehydrate_file(self, bundle):
return '/api/v1/pic/%s/file/' % (bundle.obj.id)
Just make sure the fake API view appears ahead of your actual API definitions, and you should be all set!
Paul's hint was very useful. Here i have implemented this completely in tastypie manner for uploading and downloading images.
Here you go..
1. Overriding deseriazer to support 'multipart'.
class MultipartResource(object):
def deserialize(self, request, data, format=None):
if not format:
format = request.META.get('CONTENT_TYPE', 'application/json')
if format == 'application/x-www-form-urlencoded':
return request.POST
if format.startswith('multipart'):
data = request.POST.copy()
data.update(request.FILES)
return data
return super(MultipartResource, self).deserialize(request, data, format)
2. Model class
class Research(Document):
user = ReferenceField(User)
academic_year = StringField(max_length=20)
subject = StringField(max_length=150)
topic = StringField(max_length=50)
pub_date = DateTimeField()
authored = StringField(max_length=20)
research_level = StringField(max_length=20)
paper_presented = BooleanField()
thesis_written = BooleanField()
proof_image = ImageField()
3. Resource class
class ResearchResource(MultipartResource, MongoEngineResource):
class Meta:
queryset = Research.objects.all()
list_allowed_methods = ['get','post']
resource_name = 'research'
authentication = SessionAuthentication()
authorization = Authorization()
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/$" % self._meta.resource_name,
self.wrap_view('dispatch_list'), name="api_dispatch_list"),
#url to download image file.
url(r"^(?P<resource_name>%s)/(?P<pk>\w+)/file/$"% self._meta.resource_name,
self.wrap_view('get_image'), name="api_get_image"),
]
#Preparing image url dynamically
def dehydrate_proof_image(self, bundle):
return '/api/v1/%s/%s/file/' % (self._meta.resource_name,bundle.obj.id)
#view will call based on image url to download image.
def get_image(self, request, **kwargs):
obj = Research.objects.get(id=kwargs['pk'])
image_file = obj.proof_image
return HttpResponse(image_file.read(), content_type="image/jpeg"))
Hope this will be very useful for everyone in future. :)

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.