# coding=utf-8 from __future__ import unicode_literals import json import os import ldap from django.conf import settings from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.core.cache import cache from django.db.models import Q from django.http.response import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect, Http404, \ HttpResponseForbidden from django.shortcuts import render, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib.auth.models import User from django.views.decorators.http import require_POST from django.views.decorators.cache import cache_control from adim.models import AnObj, AnObjMembership from adim.permissions import get_permission_class, has_anobj_access, SHARING_MODE_NONE from .forms import UploadImageFileForm from sendfile import sendfile from .utils import add_image_border, create_image_thumbnail def home(request): """ Home page :param request: :return: """ context = {} if request.user.is_anonymous(): context['next'] = request.GET.get('next', "") return render(request, "adim/home.html", context) def handle_404(request): if request.user.is_authenticated(): return HttpResponseRedirect(reverse("adim_app:annotate-new")) else: return HttpResponseRedirect(reverse('adim_app:home')) @login_required() def essai(request): """ Page d'essais :param request: :return: """ return render(request, "adim/essai.html", {}) # return render(request, "adim_app/essai.html", {}) @login_required def _get_anobj(request, anobj_uuid=None, anobj_id=None): """ Returns an AnObj with the uuid or id passed in parameters if the request.user has sufficient permissions for it Raise a 404 if it cannot return the AnObj :param request: :param anobj_uuid: :return: AnObj :raise: PermissionDenied """ q = {} if anobj_uuid is not None: q['uuid'] = anobj_uuid elif anobj_id is not None: q['id'] = anobj_id if not q: raise Http404() anobj = get_object_or_404(AnObj, **q) if not has_anobj_access(request, anobj): raise PermissionDenied() return anobj @login_required @cache_control(public=True, max_age=120) def send_anobj_img(request, anobj_uuid): try: anobj = _get_anobj(request, anobj_uuid=anobj_uuid) return sendfile(request, anobj.image.path) except Http404: return HttpResponseForbidden('Sorry, you cannot access this file') @login_required def anobj_thumb(request, anobj_uuid): if request.method == 'GET': return send_anobj_thumb(request, anobj_uuid=anobj_uuid) elif request.method == 'POST': return upload_anobj_thumb(request, anobj_uuid=anobj_uuid) @login_required @cache_control(public=True, max_age=1) def send_anobj_thumb(request, anobj_uuid): try: anobj = _get_anobj(request, anobj_uuid=anobj_uuid) thumb_name = "{name}__{user}.png".format(name=os.path.splitext(anobj.image.name)[0], user=request.user.id) thumb_path = os.path.join( settings.MEDIA_ROOT, thumb_name ) # If there is not yet a thumbnail for the current user # return the original one if not os.path.isfile(thumb_path): thumb_name = "{name}__.png".format(name=os.path.splitext(anobj.image.name)[0]) thumb_path = os.path.join( settings.MEDIA_ROOT, thumb_name ) return sendfile(request, thumb_path) except Http404: return HttpResponseForbidden('Sorry, you cannot access this file') @login_required def annotate_new(request): return render(request, "adim/annotation_new.html", {}) @login_required def annotate(request, anobj_uuid=None): """ Annotation page :param request: :param anobj_uuid: :return: """ context = { 'membership': False } try: anobj = AnObj.objects.select_related('owner').get(uuid__startswith=anobj_uuid) except AnObj.DoesNotExist: raise Http404() # In case of fragmentary uuid, redirect to the url with full uuid if len(anobj_uuid) < 32: return HttpResponseRedirect(reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj.uuid})) # is_owner = request.user == anobj.owner # is_owner = request.user in anobj.owners.all() is_owner = anobj.is_owned(request.user.id) context.update({'is_owner': is_owner}) # Detailed check for permissions membership = None if is_owner: if anobj.sharing_mode != SHARING_MODE_NONE: membership, _ = AnObjMembership.objects.get_or_create(anobj=anobj, user=request.user) else: # User is guest perm_class = get_permission_class(anobj.sharing_mode) if perm_class is None: # AnObj not shared raise Http404() # raise PermissionDenied() elif not perm_class.has_permission(request, anobj): # AnObj shared but user has no permission if perm_class.has_interactive_registration: # Interactive registration exists, call it return perm_class.get_interactive_registration_response(request, anobj) # No interactive registration raise PermissionDenied() else: # AnObj shared, user registred, go on pass membership = AnObjMembership.objects.get(anobj=anobj, user=request.user) if request.method == 'POST': return HttpResponseRedirect(reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj_uuid})) context.update({ 'membership': membership, 'anobj': anobj }) # Determine if we may display shared annotations if is_owner: owner_membership = membership else: try: owner_membership = AnObjMembership.objects.get(anobj=anobj, user=anobj.owner) except AnObjMembership.DoesNotExist: owner_membership = None context.update({'display_shared_annotations': (anobj.sharing_mode != SHARING_MODE_NONE) and ( # (request.user == anobj.owner) or anobj.is_owned(request.user.id) or anobj.allow_public_publishing or ( owner_membership and owner_membership.publish_mode == 2 ) ) }) return render(request, "adim/annotation.html", context) @login_required def upload_file(request, anobj_uuid=None): """ -- inspired by: https://github.com/miki725/Django-jQuery-File-Uploader-Integration-demo/blob/master/upload/views.py :param request: :return: """ if request.method != 'POST': return HttpResponseBadRequest() response_type = "application/json" response_data = {} form = UploadImageFileForm(request.POST, request.FILES) if form.is_valid(): image_file = request.FILES['image_file'] file_response = _validate_uploaded_file(image_file) response_data.update({ 'error': file_response.get('error'), 'files': [file_response], }) # Create AnObj if not file_response['error']: try: anobj = _get_anobj(request, anobj_uuid=anobj_uuid) anobj.image = image_file anobj.save() except Http404: anobj = AnObj.objects.create( owner=request.user, name=os.path.splitext(image_file.name)[0], image=image_file ) # Create original thumbnail, returned to user who has not yet annotated create_image_thumbnail(anobj.image.path) response_data['next'] = reverse('adim_app:annotate', kwargs={'anobj_uuid': anobj.uuid}) # Needed when using iFrame transport if "text/html" in request.META["HTTP_ACCEPT"]: response_type = "text/html" else: response_data['error'] = "invalid" if request.is_ajax(): return HttpResponse(json.dumps(response_data), content_type=response_type) else: return HttpResponseRedirect(response_data['next']) def _validate_uploaded_file(image_file): options = { # the maximum file size (must be in bytes) "maxfilesize": settings.ADIM_UPLOAD_MAX_FILESIZE * 2 ** 20, # 2 Mb # the file types which are going to be allowed for upload # must be a mimetype "acceptedformats": ( "image/jpeg", "image/jpg", "image/png", ) } error_id = False if image_file.size > options["maxfilesize"]: error_id = "maxFileSize" # allowed file type if image_file.content_type not in options["acceptedformats"]: error_id = "acceptFileTypes" response_data = { "name": image_file.name, "size": image_file.size, "type": image_file.content_type, "error": error_id, } return response_data @login_required @require_POST def upload_anobj_thumb(request, anobj_uuid=None): if anobj_uuid: anobj = _get_anobj(request, anobj_uuid=anobj_uuid) else: try: anobj_id = int(request.POST.get('aid')) anobj = _get_anobj(request, anobj_id=anobj_id) except TypeError: return HttpResponseBadRequest() thumb_name = "{name}__{user}.png".format(name=os.path.splitext(anobj.image.name)[0], user=request.user.id) # thumb_name = "{name}__.png".format(name=os.path.splitext(anobj.image.name)[0]) thumb_path = os.path.join(settings.MEDIA_ROOT, thumb_name) # thumb_url = reverse('adim.app:ao_thumb', kwargs={'anobj_uuid': anobj.uuid}) thumb_file = request.FILES['file'] response_data = _validate_uploaded_file(thumb_file) if response_data['error']: return HttpResponseBadRequest() with open(thumb_path, 'wb+') as destination: for chunk in thumb_file.chunks(): destination.write(chunk) add_image_border(thumb_path, save=True) return HttpResponse() def _handle_uploaded_file(image_file, destination): destination_path = os.path.join(settings.MEDIA_ROOT, destination, image_file.name) with open(destination_path, 'wb+') as destination: for chunk in image_file.chunks(): destination.write(chunk) @login_required def suggest_users(request): """ Return a list of usernames that match a query passed in as a query string This is the end point for the Bloodhound suggestion engine used for user suggestion while adding users to a shared AnObj :param request: :return: """ query_str = request.GET.get('q', "") # tokens = filter(bool, re.compile("\W+").split(query_str)) ## Use this for nonword limit instead of whitespace tokens = query_str.split() matching_users = [] usernames = [] if not tokens: return HttpResponse(content=json.dumps(matching_users), content_type="application/json") # -- Search for local users q = Q() for token in tokens: q = q & Q(username__icontains=token) for user in User.objects.filter(q): matching_users.append({'username': user.username, 'id': user.id}) usernames.append(user.username) # -- Search for ldap users if len(matching_users) < settings.ADIM_SUGGESTION['LIMIT']: cache_key = "ldapusers_" + "_".join(tokens) ldap_users = cache.get(cache_key) if ldap_users is None: filter_str = "(mail=*{}*)".format("*".join(tokens)) print "\n{h} HIT LDAP: {q} {h}\n".format(h="#" * 30, q=filter_str) ldap_object = ldap.initialize(settings.ADIM_SUGGESTION['LDAP']['URL']) try: results = ldap_object.search_st( base=settings.ADIM_SUGGESTION['LDAP']['BASE'], scope=ldap.SCOPE_SUBTREE, filterstr=filter_str, attrlist=(str("mail"),), timeout=settings.ADIM_SUGGESTION['LDAP']['TIMEOUT'] ) except ldap.TIMEOUT: results = [] ldap_users = map(lambda r: r[1].get('mail', [""])[0], results) # ldap_users = [ # "Julien.Furrer@unil.ch", # "Julien.Furrer.1@unil.ch", # "Julien.Furrer.2@unil.ch", # ] cache.set(cache_key, ldap_users, 3600 * 24) matching_users += [ {'username': user} for user in ldap_users if user not in usernames ] return HttpResponse(content=json.dumps(matching_users), content_type="application/json")