123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731 |
- from django.shortcuts import render, redirect, get_object_or_404
- from django.views import generic
- from django.views.generic.edit import CreateView, UpdateView, DeleteView
- from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
- from django.urls import reverse, reverse_lazy
- from django.contrib.auth.models import User
- from django.contrib.auth import authenticate, login
- from django.contrib.auth.mixins import LoginRequiredMixin
- from django.contrib.auth.decorators import login_required
- from django.contrib.sessions.backends.base import SessionBase
- from django.core.serializers.json import DjangoJSONEncoder
- from django.core.serializers import serialize
- from django.core import serializers
- from django.utils.html import strip_tags
- from django.db.models import Q
- from channels.db import database_sync_to_async
- from django.db.models.query import EmptyQuerySet
- from .models import Canvas, CanvasTag, Idea, IdeaComment, Project
- from .forms import SignUpForm, IdeaForm, CommentForm, AddUserForm
- from . import consumers
- from channels.layers import get_channel_layer
- from asgiref.sync import async_to_sync
- from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer, AsyncConsumer
- import django.utils.timezone
- # TODO: change serialization methods from needing to pass a singleton list to accepting a single model instance (each marked below)
- '''
- TODO: nullable owner field, blank admins & users - this is for a 'blank' project that 'blank' canvasses use, which the trial user
- uses. The selected trial canvas is currently downloaded as any other canvas, and a new idea is created like other ones, but the idea
- is not added to the canvas model in the database. It is just so that a blank idea can be sent back to the trial user and appended
- to the list of ideas in the front-end. This is why View functions, when testing for user permissions, also check for project.title == blank-project
- This approach was written as a 'quick-fix', a better solution should be investigated.
- '''
- ##################################################################################################################################
- # CANVAS VIEWS #
- ##################################################################################################################################
- def new_canvas(request, canvas_type):
- creator = request.user
- if creator.is_authenticated:
- split_url = request.META.get('HTTP_REFERER').split('/')
- project_pk = split_url[len(split_url) - 2]
- project = Project.objects.get(pk=project_pk)
- # canvas_type integer: 0 for Ethics, 1 for Business, 2 for Privacy
- canvas = Canvas(canvas_type=canvas_type, project=project)
- canvas.save()
- canvas.title = f'New Canvas {canvas.pk} (Ethics)' if canvas_type == 0 else f'New Canvas {canvas.pk} (Business)' if canvas_type == 1 else f'New Canvas {canvas.pk} (Privacy)'
- canvas.save()
- return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
- else:
- # NOTE: code below relates to blank project
- # check that a blank canvas exists - this will be used to render a blank canvas for the anonymous user to interact with
- if Project.objects.filter(title='blank-project').exists() == False:
- project = Project(title='blank-project')
- project.save()
- else:
- project = Project.objects.get(title='blank-project', is_public=False)
- if canvas_type == 0:
- if Canvas.objects.filter(title='blank-ethics').exists():
- return redirect(Canvas.objects.get(title='blank-ethics').get_absolute_url())
- else :
- # if there is no blank canvas, create one. set public to false so that it remains blank
- canvas = Canvas(title='blank-ethics', canvas_type=canvas_type, project=project)
- canvas.save()
- return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
- elif canvas_type == 1:
- if Canvas.objects.filter(title='blank-business').exists():
- return redirect(Canvas.objects.get(title='blank-business').get_absolute_url())
- else :
- # if there is no blank canvas, create one. set public to false so that it remains blank
- canvas = Canvas(title='blank-business', canvas_type=canvas_type, project=project)
- canvas.save()
- return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
- elif canvas_type == 2:
- if Canvas.objects.filter(title='blank-privacy').exists():
- return redirect(Canvas.objects.get(title='blank-business').get_absolute_url())
- else :
- # if there is no blank canvas, create one. set public to false so that it remains blank
- canvas = Canvas(title='blank-privacy', canvas_type=canvas_type, project=project)
- canvas.save()
- return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
- def new_project(request):
- creator = request.user
- project = Project(owner=creator)
- project.save()
- project.title = f'Project {project.pk}'
- project.admins.add(creator)
- project.users.add(creator)
- project.save()
- return redirect(project.get_absolute_url())
- def delete_canvas(request, pk):
- '''
- Function for deleting a canvas
- '''
- user = request.user
- canvas = Canvas.objects.get(pk = pk)
- tags = canvas.tags.all()
- for tag in tags:
- tag.canvas_set.remove(canvas)
- tag.idea_set.remove(idea__canvas__pk=canvas)
- tag.save()
- if (not admin_permission(user, canvas.project) or (project.title == 'blank-project')):
- return HttpResponse('Forbidden', status=403)
- canvas.delete()
- return redirect(request.META.get('HTTP_REFERER'))
- def delete_project(request, pk):
- '''
- Function for deleting a canvas
- '''
- user = request.user
- project = Project.objects.get(pk = pk)
- if (project.owner != user):
- return HttpResponse('Forbidden', status=403)
- project.delete()
- return redirect(request.META.get('HTTP_REFERER'))
- ##################################################################################################################################
- # CANVAS CLASS-BASED VIEWS #
- ##################################################################################################################################
- class ProjectListView(LoginRequiredMixin, generic.ListView):
- model = Project
- def get_context_data(self, **kwargs):
- '''
- This function's purpose is to separate the public from the private canvases
- '''
- logged_in_user = self.request.user
- context = super().get_context_data(**kwargs)
- filter_kwargs = {'is_public': True}
- user_filter_kwargs = {'users': logged_in_user}
- admin_filter_kwargs = {'admins': logged_in_user}
- # public projects are those where public is true
- public = Project.objects.filter(**filter_kwargs)
- # private projects where the user is either the owner or a collaborator on the canvas
- private = Project.objects.exclude(**filter_kwargs)
- my_private = (
- private.filter(**admin_filter_kwargs) | private.filter(**user_filter_kwargs)
- ).distinct()
- all_projects = (
- Project.objects.filter(**filter_kwargs) | private.filter(**admin_filter_kwargs) | private.filter(**user_filter_kwargs)
- ).distinct()
- context['public_projects'] = public
- context['private_projects'] = my_private
- context['all_projects'] = all_projects
- return context
- class ProjectDetailView(generic.DetailView):
- model = Project
- def get(self, request, pk):
- logged_in_user = self.request.user
- project = Project.objects.get(pk=pk)
- if (not user_permission(logged_in_user, project)):
- return {
- 'error': 401,
- 'response': 'unauthorized'
- }
- if request.is_ajax():
- json_users = '""'
- json_admins = '""'
- users = project.users.all()
- admins = project.admins.all()
- current = [logged_in_user]
- json_admins = serialize(
- 'json',
- admins,
- cls = UserModelEncoder
- )
- json_users = serialize(
- 'json',
- users,
- cls = UserModelEncoder
- )
- json_self=serialize(
- 'json',
- current,
- cls=UserModelEncoder
- )
- # single user - remove enclosing square brackets
- json_self = json_self[1:-1]
- data = {
- 'admins': json_admins,
- 'users': json_users,
- 'loggedInUser': json_self,
- }
- return JsonResponse(data, safe = False)
- else:
- canvas_list = Canvas.objects.filter(project=project)
- return render(
- request,
- 'catalog/project_detail.html',
- {
- 'user': logged_in_user,
- 'canvases': canvas_list
- }
- )
- class CanvasDetailView(generic.DetailView):
- model = Canvas
- def get(self, request, pk):
- '''
- function for post requests, sent by a canvas on loading
- purpose is to return the canvas information as a JSON
- '''
- logged_in_user = request.user
- canvas_pk = pk
- canvas = Canvas.objects.get(pk = canvas_pk)
- project = canvas.project
- # no user permission and the canvas isn't the blank one
- if (not user_permission(logged_in_user, project) and 'blank-' not in canvas.title):
- return {
- 'error': 401,
- 'response': 'unauthorized'
- }
- if request.is_ajax():
- # initialise every json_object as the empty string
- null_tag = CanvasTag(label=None)
- json_comments = '""'
- json_ideas = '""'
- json_tags = '""'
- json_self = '""'
- json_users = '""'
- json_admins = '""'
- if (logged_in_user.is_authenticated):
- current = [logged_in_user]
- else:
- current = project.users.none()
- comments = "''"
- ideas = Idea.objects.filter(canvas=canvas)
- # every tag attached to an idea in this current canvas
- tags = CanvasTag.objects.filter(idea_set__in=ideas).distinct()
- # every tag in the entire project
- all_tags = CanvasTag.objects.filter(canvas_set__in=Canvas.objects.filter(project=project)).distinct()
- # initialise these as empty lists - they will become lists of lists as each tag may have several tagged ideas and canvases
- tagged_ideas_json = []
- tagged_canvases_json = []
- for t in tags:
- '''
- Only need the tagged ideas and canvasses for the tags that will actually be rendered ie the tags in the current canvas
- '''
- tagged_ideas_json.append(
- serialize(
- 'json',
- t.idea_set.all(),
- cls=IdeaEncoder
- )
- )
- tagged_canvases_json.append(
- serialize(
- 'json',
- t.canvas_set.all(),
- cls=CanvasEncoder
- )
- )
- comments = IdeaComment.objects.filter(idea__in=ideas)
- # need the users list for the comment authors, when the comment is parsed the PK of the user is what's used for the user FK, not the user object itself
- users = project.users.all()
- # also need the admins for enabling or disabling certain buttons
- admins = project.admins.all()
- if tags:
- json_tags = serialize(
- 'json',
- tags,
- cls = CanvasTagEncoder
- )
- else:
- # a null tag is used for conditionally rendering the tag Vue element
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_tags = serialize(
- 'json',
- [null_tag],
- cls = CanvasTagEncoder
- )
- # singular tag, replace enclosing square brackets with curly brackets
- json_tags = json_tags[1:-1]
- if all_tags:
- json_all_tags = serialize(
- 'json',
- all_tags,
- cls = CanvasTagEncoder
- )
- else:
- # a null tag is used for conditionally rendering the tag Vue element
- tag = CanvasTag(label=None)
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_all_tags = serialize(
- 'json',
- [null_tag],
- cls = CanvasTagEncoder
- )
- # singular tag, remove enclosing square brackets
- json_all_tags = json_all_tags[1:-1]
- if comments:
- json_comments = serialize(
- 'json',
- comments,
- cls = IdeaCommentEncoder
- )
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- # NB: current assigned above as either empty string if Anon. user or [logged_in_user] if is_authenticated
- json_self=serialize(
- 'json',
- current,
- cls=UserModelEncoder
- )
- if logged_in_user.is_authenticated:
- # singular user, remove enclosing square brackets
- json_self = json_self[1:-1]
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_canvas=serialize(
- 'json',
- [canvas],
- cls=CanvasEncoder
- )
- # singular canvas, remove enclosing square brackets
- json_canvas = json_canvas[1:-1]
- json_users=serialize(
- 'json',
- users,
- cls=UserModelEncoder
- )
- json_admins=serialize(
- 'json',
- admins,
- cls=UserModelEncoder
- )
- # only serialise ideas if they exist
- if ideas:
- json_ideas = serialize(
- 'json',
- ideas,
- cls = IdeaEncoder
- )
- data = {
- 'ideas': json_ideas,
- 'comments': json_comments,
- 'tags': json_tags,
- 'allTags': json_all_tags,
- 'loggedInUser': json_self,
- 'allTaggedIdeas': tagged_ideas_json,
- 'taggedCanvases': tagged_canvases_json,
- 'thisCanvas': json_canvas,
- 'projectPK': project.pk,
- 'users': json_users,
- 'admins': json_admins,
- }
- return JsonResponse(data, safe = False)
- else:
- '''
- This 'else' is for the initial page load. document.onload currently triggers the AJAX GET request which gets all
- the relevant canvas information as JSON objects
- '''
- return render(
- request,
- 'catalog/canvas_detail.html',
- {
- 'user': logged_in_user,
- 'project': project,
- },
- )
- ##################################################################################################################################
- # IDEA VIEWS #
- ##################################################################################################################################
- def new_trial_idea(request):
- '''
- NOTE: BLANK CANVAS, TRIAL USER IDEA ADDITION
- '''
- if request.method == 'POST':
- print("..")
- canvas_pk = request.POST['canvas_pk']
- try:
- canvas = Canvas.objects.get(pk=canvas_pk)
- except Canvas.DoesNotExist:
- return HttpResponse('Canvas does not exist', status=404)
- logged_in_user = request.user
- project = canvas.project
- # can't add ideas if the canvas is unavailable or if the blank canvas is being edited to by an authenticated user
- if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
- return HttpResponse('Unauthorized', status=401)
- category = request.POST['idea_category']
- idea = Idea(
- canvas = canvas,
- category = category,
- text = '',
- title = f'Canvas {canvas_pk} TRIAL IDEA'
- )
- idea.save()
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- return_idea = serialize(
- 'json',
- [idea],
- cls=IdeaEncoder
- )
- idea.delete()
- # singular idea, remove enclosing square brackets
- return_idea = return_idea[1:-1]
- data = {
- 'idea': return_idea,
- }
- return JsonResponse(data)
- def delete_trial_idea(idea_pk):
- '''
- NOTE: BLANK CANVAS, TRIAL USER IDEA DELETION - IMM
- '''
- Idea.objects.get(pk=idea_pk).delete()
- def new_idea(request):
- '''
- Creation of a new idea. This gets the id for the canvas in which it is created from the calling URL
- '''
- if request.method == 'POST':
- canvas_pk = request.POST['canvas_pk']
- category = request.POST['idea_category']
- logged_in_user = request.user
- try:
- canvas = Canvas.objects.get(pk = canvas_pk)
- project = canvas.project
- except:
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- # can't add ideas if the canvas is unavailable or if the blank canvas is being edited to by an authenticated user
- if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
- return HttpResponse('Unauthorized', status=401)
- idea = Idea(
- canvas = canvas,
- category = category,
- text = ''
- )
- idea.save()
- # This is so I can click on it in the django admin - should probably delete later
- idea.title = f'Canvas {canvas_pk} Idea {idea.pk}'
- idea.save()
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- return_idea = serialize(
- 'json',
- [idea],
- cls=IdeaEncoder
- )
- # singular idea, remove enclosing square brackets
- return_idea = return_idea[1:-1]
- channel_layer = get_channel_layer()
- room_name = canvas_pk + "_idea"
- room_group_name = 'canvas_%s' %room_name
- data = {
- 'function': request.POST['function'],
- 'idea': return_idea,
- }
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def delete_idea(request):
- '''
- Deletion of an idea
- '''
- if request.method == 'POST':
- try:
- logged_in_user = request.user
- idea_pk = request.POST['idea_pk']
- idea = Idea.objects.get(pk=idea_pk)
- canvas = idea.canvas
- project = canvas.project
- except:
- Idea.DoesNotExist
- return HttpResponse('Idea does not exist', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- # can't remove ideas if the canvas is unavailable or if the blank canvas is being edited by an authenticated user
- if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
- return HttpResponse('Unauthorized', status=401)
- category = idea.category
- # get every tag associated with the idea
- tags = idea.tags.all()
- removed_tags = []
- json_tagged_canvases = []
- json_tagged_ideas = []
- json_tags = []
- # iterate through the tags, removing the idea from each
- for tag in tags:
- tag.idea_set.remove(idea)
- tag.save()
- # check if any ideas remain
- updated_ideas = tag.idea_set.filter(canvas=canvas).distinct()
- # if no idaes remain, remove the canvas from the tag's canvas set as well
- if not updated_ideas:
- tag.canvas_set.remove(canvas)
- tag.save()
- canvas.save()
- removed_tags.append(tag)
- return_tag_data = []
- for tag in removed_tags:
- json_tagged_canvases=(
- serialize(
- 'json',
- tag.canvas_set.all(),
- cls=CanvasEncoder
- )
- )
- json_tagged_ideas=(
- serialize(
- 'json',
- tag.idea_set.all(),
- cls=IdeaEncoder
- )
- )
- json_tags=(
- serialize(
- 'json',
- [tag],
- cls = CanvasTagEncoder
- )
- )
- json_tags = json_tags[1:-1]
- tag_data = {
- 'taggedCanvases': json_tagged_canvases,
- 'taggedIdeas': json_tagged_ideas,
- 'tags': json_tags,
- }
- return_tag_data.append(tag_data)
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- return_idea = serialize(
- 'json',
- [idea],
- cls=IdeaEncoder
- )
- # singular idea, remove enclosing square brackets
- return_idea = return_idea[1:-1]
- idea.delete()
- data = {
- 'function': request.POST['function'],
- 'returnTagData': return_tag_data,
- 'idea': return_idea,
- 'ideaCategory': category,
- 'ideaListIndex': request.POST['idea_list_index']
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_idea"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def edit_idea(request):
- '''
- Update of an idea
- '''
- if request.method == 'POST':
- try:
- logged_in_user = request.user
- idea_pk = request.POST['idea_pk']
- input_text = request.POST['input_text']
- idea = Idea.objects.get(pk = idea_pk)
- canvas = idea.canvas
- project = canvas.project
- except:
- Idea.DoesNotExist
- return HttpResponse('Idea does not exist', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- current_tags_in_idea = idea.tags.all()
- old_text = idea.text
- if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Unauthorized', status=401)
- input_text = strip_tags(input_text)
- updated_tags_in_idea = []
- new_tags = []
- new_tags_canvas_set = []
- new_tags_idea_set = []
- removed_tags = []
- removed_tags_canvas_set = []
- removed_tags_idea_set = []
- # check if any of the tags are implicitly removed or inserted by their labels no longer occurring in the idea or newly occurring in the idea respectively
- for temp_canvas in project.canvas_set.all():
- for tag in temp_canvas.tags.all():
- # if the tag is in the input_text, add it. RHS of and operation is to keep the list elements unique
- if tag.label in input_text and tag not in updated_tags_in_idea:
- updated_tags_in_idea.append(tag)
- # if it was in the old text and no longer occurs, remove it
- elif tag.label in old_text and tag.label not in input_text:
- removed_tags.append(tag)
- # update the tags field in canvas by setting them - implicitly remove the removed tags
- canvas.tags.set(updated_tags_in_idea)
- canvas.save()
- # the same for idea's tags field
- idea.tags.set(updated_tags_in_idea)
- idea.text = input_text
- idea.save()
- new_return_tag_data = []
- removed_return_tag_data = []
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- return_idea = serialize(
- 'json',
- [idea],
- cls=IdeaEncoder
- )
- return_idea = return_idea[1:-1]
- for tag in updated_tags_in_idea:
- new_tags=(
- serialize(
- 'json',
- [tag],
- cls = CanvasTagEncoder
- )
- )
- new_tags = new_tags[1:-1]
- new_tags_canvas_set=(
- serialize(
- 'json',
- tag.canvas_set.all(),
- cls=CanvasEncoder
- )
- )
- new_tags_idea_set=(
- serialize(
- 'json',
- tag.idea_set.all(),
- cls=IdeaEncoder
- )
- )
- tag_data = {
- 'newTag': new_tags,
- 'newTaggedCanvases': new_tags_canvas_set,
- 'newTaggedIdeas': new_tags_idea_set,
- }
- new_return_tag_data.append(tag_data)
- for tag in removed_tags:
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- removed_tags=(
- serialize(
- 'json',
- [tag],
- cls = CanvasTagEncoder
- )
- )
- removed_tags = removed_tags[1:-1]
- removed_tags_canvas_set=(
- serialize(
- 'json',
- tag.canvas_set.all(),
- cls=CanvasEncoder
- )
- )
- removed_tags_idea_set=(
- serialize(
- 'json',
- tag.idea_set.all(),
- cls=IdeaEncoder
- )
- )
- tag_data = {
- 'removedTag': removed_tags,
- 'removedTaggedCanvases': removed_tags_canvas_set,
- 'removedTaggedIdeas': removed_tags_idea_set,
- }
- removed_return_tag_data.append(tag_data)
- data = {
- 'function': request.POST['function'],
- 'removedReturnTagData': removed_return_tag_data,
- 'newReturnTagData': new_return_tag_data,
- 'idea': return_idea,
- 'oldText': old_text,
- 'ideaCategory': idea.category,
- 'ideaListIndex': request.POST['idea_list_index'],
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_idea"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- ##################################################################################################################################
- # COMMENT VIEWS #
- ##################################################################################################################################
- def new_comment(request):
- if request.method == 'POST':
- logged_in_user = request.user
- idea_pk = request.POST['idea_pk']
- input_text = request.POST['input_text']
- try:
- idea = Idea.objects.get(pk=idea_pk)
- canvas = idea.canvas
- project = canvas.project
- except:
- Idea.DoesNotExist
- return HttpResponse('Idea does not exist.', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Unauthorized', status=401)
- text = input_text
- text = strip_tags(text)
- comment = IdeaComment(
- user = logged_in_user,
- text = text,
- idea = idea
- )
- comment.save()
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_comment = serialize(
- 'json',
- [comment],
- cls = IdeaCommentEncoder
- )
- # singular comment, remove enclosing square brackets
- json_comment = json_comment[1:-1]
- data = {
- 'function': request.POST['function'],
- 'comment': json_comment,
- 'ideaCategory': idea.category,
- 'ideaListIndex': request.POST['idea_list_index']
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_comment"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def delete_comment(request):
- '''
- Deletion of a comment
- '''
- if request.method == 'POST':
- logged_in_user = request.user
- comment_pk = request.POST['comment_pk']
- try:
- comment = IdeaComment.objects.get(pk=comment_pk)
- canvas = comment.idea.canvas
- project = canvas.project
- except:
- IdeaComment.DoesNotExist
- return HttpResponse('Comment does not exist.', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Forbidden.', status=403)
- category = comment.idea.category
- comment.delete()
- data = {
- 'function': request.POST['function'],
- 'ideaCategory': category,
- 'ideaListIndex': request.POST['idea_list_index'],
- 'commentListIndex': request.POST['comment_list_index'],
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_comment"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def single_comment_resolve(request):
- '''
- Resolution of a single comment
- '''
- if request.method == 'POST':
- logged_in_user = request.user
- comment_pk = request.POST['comment_pk']
- try:
- comment = IdeaComment.objects.get(pk=comment_pk)
- canvas = comment.idea.canvas
- project = canvas.project
- except:
- IdeaComment.DoesNotExist
- return HttpResponse('Comment does not exist.', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Forbidden.', status=403)
- category = comment.idea.category
- comment.resolved = True
- comment.save()
- data = {
- 'function': request.POST['function'],
- 'ideaCategory': category,
- 'ideaListIndex': request.POST['idea_list_index'],
- 'commentListIndex': request.POST['comment_list_index'],
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_comment"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def all_comment_resolve(request):
- '''
- Resolution of comments - mark all as resolved
- '''
- if request.method == 'POST':
- logged_in_user = request.user
- idea_pk = request.POST['idea_pk']
- try:
- idea = Idea.objects.get(pk = idea_pk)
- canvas = idea.canvas
- project = canvas.project
- except:
- Idea.DoesNotExist
- return HttpResponse('Idea does not exist.', status=404)
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Forbidden.', status=403)
- IdeaComment.objects.all().filter(idea = idea).update(resolved=True)
- data = {
- 'function': request.POST['function'],
- 'ideaCategory': idea.category,
- 'ideaListIndex': request.POST['idea_list_index'],
- }
- channel_layer = get_channel_layer()
- room_name = f"{canvas.pk}_comment"
- room_group_name = 'canvas_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- ##################################################################################################################################
- # COLLABORATOR AND LANDING PAGE VIEWS #
- ##################################################################################################################################
- def index(request):
- return render(request, 'index.html')
- def register(request):
- if request.method == 'POST':
- form = SignUpForm(request.POST)
- if form.is_valid():
- username = form.cleaned_data['name']
- email = form.cleaned_data['email']
- password = form.cleaned_data['password']
- newUser = User.objects.create_user(
- username = username,
- email = email,
- password = password
- )
- return HttpResponseRedirect(reverse('index'))
- else:
- form = SignUpForm(initial = {
- 'name': '',
- 'email': '',
- 'password': '',
- 'password2': '',
- })
- return render(
- request,
- 'catalog/register.html',
- {'form': form}
- )
- def add_user(request):
- '''
- Function for addition of user to project
- '''
- if request.method == 'POST':
- project_pk = request.POST['project_pk']
- try:
- project = Project.objects.get(pk=project_pk)
- except:
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- name = request.POST['name']
- logged_in_user = request.user
- # check is admin
- if (not admin_permission(logged_in_user, project)):
- return HttpResponse('Forbidden.', status=403)
- else:
- try:
- user = User.objects.get(username=name)
- except:
- User.DoesNotExist
- return HttpResponse('User does not exist.', status=404)
- if user in project.users.all() or user in project.admins.all():
- reply = ''
- if user is logged_in_user:
- reply = 'Error: you\'re already a collaborator, you can\'t add yourself!'
- else:
- reply = 'Error: ' + name + ' is already a collaborator!'
- return HttpResponse(reply, status=500)
- project.users.add(user)
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_user = serialize(
- 'json',
- [user],
- cls = UserModelEncoder
- )
- # singular user - remove enclosing square brackets
- json_user = json_user[1:-1]
- data = {
- 'function': request.POST['function'],
- 'user': json_user,
- }
- channel_layer = get_channel_layer()
- room_name = project_pk + "_collab"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def delete_user(request):
- '''
- Function for deleting a user from the project.
- '''
- if request.method == 'POST':
- project_pk = request.POST['project_pk']
- try:
- project = Project.objects.get(pk=project_pk)
- except:
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- logged_in_user = request.user
- # check is admin
- if (not admin_permission(logged_in_user, project)):
- return HttpResponse('Forbidden', status=403)
- try:
- user = User.objects.get(pk=request.POST['user_pk'])
- except:
- User.DoesNotExist
- return HttpResponse('User does not exist.', status=404)
- if user not in project.users.all():
- reply = 'Error: ' + name + ' is not a collaborator'
- return HttpResponse(reply, status=500)
- admins = project.admins.all()
- # if there is one admin who is the logged-in user, do not allow them to
- # delete themselves. It's implied that if there's one admin, the logged_in
- # user is that admin, as earlier it is checked that the logged_in user
- # is in the project admin set
- if (len(admins) == 1 and user in admins):
- reply = 'Error: You are the only admin, you may not delete yourself!'
- return HttpResponse(reply, status=500)
- victim_is_admin = "false"
- # if the user is also an admin, remove them from that field also
- if user in admins:
- victim_is_admin = "true"
- project.admins.remove(user)
- project.users.remove(user)
- data = {
- 'function': request.POST['function'],
- 'userListIndex': request.POST['user_list_index'],
- 'victimIsAdmin': victim_is_admin,
- }
- channel_layer = get_channel_layer()
- room_name = project_pk + "_collab"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def promote_user(request):
- '''
- Function for promoting a user to admin status
- '''
- if request.method == 'POST':
- project_pk = request.POST['project_pk']
- try:
- project = Project.objects.get(pk=project_pk)
- except:
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- logged_in_user = request.user
- # check is admin
- if (not admin_permission(logged_in_user, project)):
- return HttpResponse('Forbidden', status=403)
- try:
- user = User.objects.get(pk=request.POST['user_pk'])
- except:
- User.DoesNotExist
- return HttpResponse('User does not exist', status=404)
- name_str = user.username
- admins = project.admins.all()
- # check presence in admin set
- if user in admins:
- # additionally check the user isn't trying to promote themselves
- if user is logged_in_user:
- name_str = 'you are'
- else:
- name_str = name_str + ' is '
- reply = 'Error: ' + name_str + ' already an admin!'
- return HttpResponse(reply, status=500)
- project.admins.add(user)
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_user = serialize(
- 'json',
- [user],
- cls = UserModelEncoder
- )
- # singular user - remove enclosing square brackets
- json_user = json_user[1:-1]
- data = {
- 'function': request.POST['function'],
- 'admin': json_user,
- }
- channel_layer = get_channel_layer()
- room_name = project_pk + "_collab"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def demote_user(request):
- '''
- Function to delete a user from the admin field - this is for demotion only.
- For complete deletion, call delete user
- '''
- if request.method == 'POST':
- project_pk = request.POST['project_pk']
- try:
- project = Project.objects.get(pk=project_pk)
- except:
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- logged_in_user = request.user
- # check is admin
- if (not admin_permission(logged_in_user, project)):
- return HttpResponse('Forbidden', status=403)
- try:
- user = User.objects.get(pk=request.POST['user_pk'])
- except:
- User.DoesNotExist
- return HttpResponse('User does not exist', status=404)
- admins = project.admins.all()
- # Can't delete a non-existent admin
- if user not in admins:
- reply = 'Error: ' + name + ' is not an admin'
- return HttpResponse(reply, status=500)
- # if there is one admin who is the logged-in user, do not allow them to
- # delete themselves
- if len(admins) == 1:
- reply = 'Error: You are the only admin, you may not demote yourself!'
- return HttpResponse(reply, status=500)
- project.admins.remove(user)
- data = {
- 'function': request.POST['function'],
- 'adminListIndex': request.POST['admin_list_index']
- }
- channel_layer = get_channel_layer()
- room_name = project_pk + "_collab"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def toggle_public(request):
- if request.method == 'POST':
- project_pk = request.POST['project_pk']
- try:
- project = Project.objects.get(pk=project_pk)
- except:
- Project.DoesNotExist
- return HttpResponse('Project does not exist', status=404)
- logged_in_user = request.user
- # check is admin
- if (not admin_permission(logged_in_user, project)):
- return HttpResponse('Forbidden', status=403)
- project.is_public = not(project.is_public)
- project.save()
- data = {
- 'function': request.POST['function'],
- }
- channel_layer = get_channel_layer()
- room_name = project_pk + "_collab"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- ##################################################################################################################################
- # TAG VIEWS #
- ##################################################################################################################################
- def add_tag(request):
- '''
- ADDITION OF NEW TAG
- '''
- if request.method == 'POST':
- try:
- canvas = Canvas.objects.get(pk=request.POST['canvas_pk'])
- print(canvas)
- project = canvas.project
- except:
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- logged_in_user = request.user
- label = request.POST['label']
- if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Unauthorized', status=401)
- # check existence of tag within project - avoid duplicating tags
- if CanvasTag.objects.filter(label=label, canvas_set__project=project).exists():
- tag = CanvasTag.objects.get(label=label)
- tag_canvas_set = tag.canvas_set.all()
- if canvas not in tag_canvas_set:
- tag.canvas_set.add(canvas)
- else:
- # only create tag if it doesn't exist anywhere visible to the user
- tag = CanvasTag(label=label)
- tag.save()
- tag.canvas_set.add(canvas)
- tag.save()
- canvas.save()
- # check every canvas for presence of new tag's label in those canvases on creation of new tag
- for canvas in project.canvas_set.all():
- ideas = Idea.objects.filter(canvas=canvas)
- for idea in ideas:
- if tag.label in idea.text:
- if idea not in tag.idea_set.all():
- tag.idea_set.add(idea)
- idea.save()
- # skip the below step if the above is false
- if canvas not in tag.canvas_set.all():
- canvas.save()
- tag.canvas_set.add(canvas)
- canvas.save()
- # save tag if modifications made
- tag.save()
- tags = CanvasTag.objects.filter(canvas_set__project=project).distinct()
- json_tagged_canvases = []
- json_tagged_ideas = []
- # for t in tags:
- json_tagged_canvases.append(
- serialize(
- 'json',
- tag.canvas_set.all().order_by('-id'),
- cls=CanvasEncoder
- )
- )
- json_tagged_ideas.append(
- serialize(
- 'json',
- tag.idea_set.all().order_by('canvas'),
- cls=IdeaEncoder
- )
- )
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_tag = serialize(
- 'json',
- [tag],
- cls = CanvasTagEncoder
- )
- # singular tag - remove enclosing square brackets
- json_tag = json_tag[1:-1]
- data = {
- 'function': request.POST['function'],
- 'taggedCanvases': json_tagged_canvases,
- 'taggedIdeas': json_tagged_ideas,
- 'tag': json_tag,
- }
- channel_layer = get_channel_layer()
- room_name = f"{project.pk}_tag"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- def delete_tag(request):
- '''
- DELETION OF TAG
- '''
- if request.method == 'POST':
- try:
- canvas = Canvas.objects.get(pk=request.POST['canvas_pk'])
- project = canvas.project
- except:
- Canvas.DoesNotExist
- return HttpResponse('Canvas does not exist.', status=404)
- Project.DoesNotExist
- return HttpResponse('Project does not exist.', status=404)
- logged_in_user = request.user
- label = request.POST['label']
- if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
- return HttpResponse('Unauthorized', status=401)
- try:
- tag = CanvasTag.objects.get(label=label, canvas_set=canvas)
- except:
- CanvasTag.DoesNotExist
- return HttpResponse('Tag does not exist.', status=404)
- CanvasTag.objects.filter(label=label, canvas_set__project=project).delete()
- # delete any tags that aren't attached to a canvas: they are never useful
- CanvasTag.objects.filter(canvas_set=None).delete()
- # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
- json_tag = serialize(
- 'json',
- [tag],
- cls = CanvasTagEncoder
- )
- # singular tag - remove enclosing square brackets
- json_tag = json_tag[1:-1]
- data = {
- 'function': request.POST['function'],
- 'tag': json_tag,
- }
- channel_layer = get_channel_layer()
- room_name = f"{project.pk}_tag"
- room_group_name = 'project_%s' %room_name
- async_to_sync(channel_layer.group_send)(
- room_group_name,
- {
- 'type': 'channel_message',
- 'data': data
- }
- )
- return HttpResponse(status=200)
- ##################################################################################################################################
- # MISCELLANEOUS FUNCTIONS #
- ##################################################################################################################################
- def search_canvas_for_tag(tag, canvas):
- '''
- check for presence of tag in canvas
- '''
- ideas = Idea.objects.filter(canvas=canvas)
- for idea in ideas:
- if tag.label in idea.text:
- if idea not in tag.idea_set.all():
- tag.idea_set.add(idea)
- # skip the below step if the above is false
- if canvas not in tag.canvas_set.all():
- canvas.save()
- tag.canvas_set.add(canvas)
- # save tag if modifications made
- tag.save()
- def user_permission(logged_in_user, project):
- return ((logged_in_user in project.users.all()) or (project.is_public == True))
- def admin_permission(logged_in_user, project):
- return ((logged_in_user in project.admins.all()) or (project.is_public == True))
- class IdeaEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, Idea):
- return str(obj)
- return super().default(obj)
- class IdeaCommentEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, IdeaComment):
- return str(obj)
- return super().default(obj)
- class CanvasTagEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, CanvasTag):
- return str(obj)
- return super().default(obj)
- class CanvasEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, Canvas):
- return str(obj)
- return super().default(obj)
- class ProjectModelEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, Project):
- return str(obj)
- return super().default(obj)
- class UserModelEncoder(DjangoJSONEncoder):
- def default(self, obj):
- if isinstance(obj, CanvasTag):
- return str(obj)
- return super().default(obj)
- class Meta:
- model = User
- exclude = ('password',)
|