views.py 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731
  1. from django.shortcuts import render, redirect, get_object_or_404
  2. from django.views import generic
  3. from django.views.generic.edit import CreateView, UpdateView, DeleteView
  4. from django.http import HttpResponseRedirect, JsonResponse, HttpResponse
  5. from django.urls import reverse, reverse_lazy
  6. from django.contrib.auth.models import User
  7. from django.contrib.auth import authenticate, login
  8. from django.contrib.auth.mixins import LoginRequiredMixin
  9. from django.contrib.auth.decorators import login_required
  10. from django.contrib.sessions.backends.base import SessionBase
  11. from django.core.serializers.json import DjangoJSONEncoder
  12. from django.core.serializers import serialize
  13. from django.core import serializers
  14. from django.utils.html import strip_tags
  15. from django.db.models import Q
  16. from channels.db import database_sync_to_async
  17. from django.db.models.query import EmptyQuerySet
  18. from .models import Canvas, CanvasTag, Idea, IdeaComment, Project
  19. from .forms import SignUpForm, IdeaForm, CommentForm, AddUserForm
  20. from . import consumers
  21. from channels.layers import get_channel_layer
  22. from asgiref.sync import async_to_sync
  23. from channels.generic.websocket import WebsocketConsumer, AsyncWebsocketConsumer, AsyncConsumer
  24. import django.utils.timezone
  25. # TODO: change serialization methods from needing to pass a singleton list to accepting a single model instance (each marked below)
  26. '''
  27. TODO: nullable owner field, blank admins & users - this is for a 'blank' project that 'blank' canvasses use, which the trial user
  28. uses. The selected trial canvas is currently downloaded as any other canvas, and a new idea is created like other ones, but the idea
  29. 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
  30. 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
  31. This approach was written as a 'quick-fix', a better solution should be investigated.
  32. '''
  33. ##################################################################################################################################
  34. # CANVAS VIEWS #
  35. ##################################################################################################################################
  36. def new_canvas(request, canvas_type):
  37. creator = request.user
  38. if creator.is_authenticated:
  39. split_url = request.META.get('HTTP_REFERER').split('/')
  40. project_pk = split_url[len(split_url) - 2]
  41. project = Project.objects.get(pk=project_pk)
  42. # canvas_type integer: 0 for Ethics, 1 for Business, 2 for Privacy
  43. canvas = Canvas(canvas_type=canvas_type, project=project)
  44. canvas.save()
  45. 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)'
  46. canvas.save()
  47. return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
  48. else:
  49. # NOTE: code below relates to blank project
  50. # check that a blank canvas exists - this will be used to render a blank canvas for the anonymous user to interact with
  51. if Project.objects.filter(title='blank-project').exists() == False:
  52. project = Project(title='blank-project')
  53. project.save()
  54. else:
  55. project = Project.objects.get(title='blank-project', is_public=False)
  56. if canvas_type == 0:
  57. if Canvas.objects.filter(title='blank-ethics').exists():
  58. return redirect(Canvas.objects.get(title='blank-ethics').get_absolute_url())
  59. else :
  60. # if there is no blank canvas, create one. set public to false so that it remains blank
  61. canvas = Canvas(title='blank-ethics', canvas_type=canvas_type, project=project)
  62. canvas.save()
  63. return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
  64. elif canvas_type == 1:
  65. if Canvas.objects.filter(title='blank-business').exists():
  66. return redirect(Canvas.objects.get(title='blank-business').get_absolute_url())
  67. else :
  68. # if there is no blank canvas, create one. set public to false so that it remains blank
  69. canvas = Canvas(title='blank-business', canvas_type=canvas_type, project=project)
  70. canvas.save()
  71. return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
  72. elif canvas_type == 2:
  73. if Canvas.objects.filter(title='blank-privacy').exists():
  74. return redirect(Canvas.objects.get(title='blank-business').get_absolute_url())
  75. else :
  76. # if there is no blank canvas, create one. set public to false so that it remains blank
  77. canvas = Canvas(title='blank-privacy', canvas_type=canvas_type, project=project)
  78. canvas.save()
  79. return redirect(canvas.get_absolute_url()) # bring user to the canvas page for the newly created canvas
  80. def new_project(request):
  81. creator = request.user
  82. project = Project(owner=creator)
  83. project.save()
  84. project.title = f'Project {project.pk}'
  85. project.admins.add(creator)
  86. project.users.add(creator)
  87. project.save()
  88. return redirect(project.get_absolute_url())
  89. def delete_canvas(request, pk):
  90. '''
  91. Function for deleting a canvas
  92. '''
  93. user = request.user
  94. canvas = Canvas.objects.get(pk = pk)
  95. tags = canvas.tags.all()
  96. for tag in tags:
  97. tag.canvas_set.remove(canvas)
  98. tag.idea_set.remove(idea__canvas__pk=canvas)
  99. tag.save()
  100. if (not admin_permission(user, canvas.project) or (project.title == 'blank-project')):
  101. return HttpResponse('Forbidden', status=403)
  102. canvas.delete()
  103. return redirect(request.META.get('HTTP_REFERER'))
  104. def delete_project(request, pk):
  105. '''
  106. Function for deleting a canvas
  107. '''
  108. user = request.user
  109. project = Project.objects.get(pk = pk)
  110. if (project.owner != user):
  111. return HttpResponse('Forbidden', status=403)
  112. project.delete()
  113. return redirect(request.META.get('HTTP_REFERER'))
  114. ##################################################################################################################################
  115. # CANVAS CLASS-BASED VIEWS #
  116. ##################################################################################################################################
  117. class ProjectListView(LoginRequiredMixin, generic.ListView):
  118. model = Project
  119. def get_context_data(self, **kwargs):
  120. '''
  121. This function's purpose is to separate the public from the private canvases
  122. '''
  123. logged_in_user = self.request.user
  124. context = super().get_context_data(**kwargs)
  125. filter_kwargs = {'is_public': True}
  126. user_filter_kwargs = {'users': logged_in_user}
  127. admin_filter_kwargs = {'admins': logged_in_user}
  128. # public projects are those where public is true
  129. public = Project.objects.filter(**filter_kwargs)
  130. # private projects where the user is either the owner or a collaborator on the canvas
  131. private = Project.objects.exclude(**filter_kwargs)
  132. my_private = (
  133. private.filter(**admin_filter_kwargs) | private.filter(**user_filter_kwargs)
  134. ).distinct()
  135. all_projects = (
  136. Project.objects.filter(**filter_kwargs) | private.filter(**admin_filter_kwargs) | private.filter(**user_filter_kwargs)
  137. ).distinct()
  138. context['public_projects'] = public
  139. context['private_projects'] = my_private
  140. context['all_projects'] = all_projects
  141. return context
  142. class ProjectDetailView(generic.DetailView):
  143. model = Project
  144. def get(self, request, pk):
  145. logged_in_user = self.request.user
  146. project = Project.objects.get(pk=pk)
  147. if (not user_permission(logged_in_user, project)):
  148. return {
  149. 'error': 401,
  150. 'response': 'unauthorized'
  151. }
  152. if request.is_ajax():
  153. json_users = '""'
  154. json_admins = '""'
  155. users = project.users.all()
  156. admins = project.admins.all()
  157. current = [logged_in_user]
  158. json_admins = serialize(
  159. 'json',
  160. admins,
  161. cls = UserModelEncoder
  162. )
  163. json_users = serialize(
  164. 'json',
  165. users,
  166. cls = UserModelEncoder
  167. )
  168. json_self=serialize(
  169. 'json',
  170. current,
  171. cls=UserModelEncoder
  172. )
  173. # single user - remove enclosing square brackets
  174. json_self = json_self[1:-1]
  175. data = {
  176. 'admins': json_admins,
  177. 'users': json_users,
  178. 'loggedInUser': json_self,
  179. }
  180. return JsonResponse(data, safe = False)
  181. else:
  182. canvas_list = Canvas.objects.filter(project=project)
  183. return render(
  184. request,
  185. 'catalog/project_detail.html',
  186. {
  187. 'user': logged_in_user,
  188. 'canvases': canvas_list
  189. }
  190. )
  191. class CanvasDetailView(generic.DetailView):
  192. model = Canvas
  193. def get(self, request, pk):
  194. '''
  195. function for post requests, sent by a canvas on loading
  196. purpose is to return the canvas information as a JSON
  197. '''
  198. logged_in_user = request.user
  199. canvas_pk = pk
  200. canvas = Canvas.objects.get(pk = canvas_pk)
  201. project = canvas.project
  202. # no user permission and the canvas isn't the blank one
  203. if (not user_permission(logged_in_user, project) and 'blank-' not in canvas.title):
  204. return {
  205. 'error': 401,
  206. 'response': 'unauthorized'
  207. }
  208. if request.is_ajax():
  209. # initialise every json_object as the empty string
  210. null_tag = CanvasTag(label=None)
  211. json_comments = '""'
  212. json_ideas = '""'
  213. json_tags = '""'
  214. json_self = '""'
  215. json_users = '""'
  216. json_admins = '""'
  217. if (logged_in_user.is_authenticated):
  218. current = [logged_in_user]
  219. else:
  220. current = project.users.none()
  221. comments = "''"
  222. ideas = Idea.objects.filter(canvas=canvas)
  223. # every tag attached to an idea in this current canvas
  224. tags = CanvasTag.objects.filter(idea_set__in=ideas).distinct()
  225. # every tag in the entire project
  226. all_tags = CanvasTag.objects.filter(canvas_set__in=Canvas.objects.filter(project=project)).distinct()
  227. # initialise these as empty lists - they will become lists of lists as each tag may have several tagged ideas and canvases
  228. tagged_ideas_json = []
  229. tagged_canvases_json = []
  230. for t in tags:
  231. '''
  232. Only need the tagged ideas and canvasses for the tags that will actually be rendered ie the tags in the current canvas
  233. '''
  234. tagged_ideas_json.append(
  235. serialize(
  236. 'json',
  237. t.idea_set.all(),
  238. cls=IdeaEncoder
  239. )
  240. )
  241. tagged_canvases_json.append(
  242. serialize(
  243. 'json',
  244. t.canvas_set.all(),
  245. cls=CanvasEncoder
  246. )
  247. )
  248. comments = IdeaComment.objects.filter(idea__in=ideas)
  249. # 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
  250. users = project.users.all()
  251. # also need the admins for enabling or disabling certain buttons
  252. admins = project.admins.all()
  253. if tags:
  254. json_tags = serialize(
  255. 'json',
  256. tags,
  257. cls = CanvasTagEncoder
  258. )
  259. else:
  260. # a null tag is used for conditionally rendering the tag Vue element
  261. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  262. json_tags = serialize(
  263. 'json',
  264. [null_tag],
  265. cls = CanvasTagEncoder
  266. )
  267. # singular tag, replace enclosing square brackets with curly brackets
  268. json_tags = json_tags[1:-1]
  269. if all_tags:
  270. json_all_tags = serialize(
  271. 'json',
  272. all_tags,
  273. cls = CanvasTagEncoder
  274. )
  275. else:
  276. # a null tag is used for conditionally rendering the tag Vue element
  277. tag = CanvasTag(label=None)
  278. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  279. json_all_tags = serialize(
  280. 'json',
  281. [null_tag],
  282. cls = CanvasTagEncoder
  283. )
  284. # singular tag, remove enclosing square brackets
  285. json_all_tags = json_all_tags[1:-1]
  286. if comments:
  287. json_comments = serialize(
  288. 'json',
  289. comments,
  290. cls = IdeaCommentEncoder
  291. )
  292. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  293. # NB: current assigned above as either empty string if Anon. user or [logged_in_user] if is_authenticated
  294. json_self=serialize(
  295. 'json',
  296. current,
  297. cls=UserModelEncoder
  298. )
  299. if logged_in_user.is_authenticated:
  300. # singular user, remove enclosing square brackets
  301. json_self = json_self[1:-1]
  302. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  303. json_canvas=serialize(
  304. 'json',
  305. [canvas],
  306. cls=CanvasEncoder
  307. )
  308. # singular canvas, remove enclosing square brackets
  309. json_canvas = json_canvas[1:-1]
  310. json_users=serialize(
  311. 'json',
  312. users,
  313. cls=UserModelEncoder
  314. )
  315. json_admins=serialize(
  316. 'json',
  317. admins,
  318. cls=UserModelEncoder
  319. )
  320. # only serialise ideas if they exist
  321. if ideas:
  322. json_ideas = serialize(
  323. 'json',
  324. ideas,
  325. cls = IdeaEncoder
  326. )
  327. data = {
  328. 'ideas': json_ideas,
  329. 'comments': json_comments,
  330. 'tags': json_tags,
  331. 'allTags': json_all_tags,
  332. 'loggedInUser': json_self,
  333. 'allTaggedIdeas': tagged_ideas_json,
  334. 'taggedCanvases': tagged_canvases_json,
  335. 'thisCanvas': json_canvas,
  336. 'projectPK': project.pk,
  337. 'users': json_users,
  338. 'admins': json_admins,
  339. }
  340. return JsonResponse(data, safe = False)
  341. else:
  342. '''
  343. This 'else' is for the initial page load. document.onload currently triggers the AJAX GET request which gets all
  344. the relevant canvas information as JSON objects
  345. '''
  346. return render(
  347. request,
  348. 'catalog/canvas_detail.html',
  349. {
  350. 'user': logged_in_user,
  351. 'project': project,
  352. },
  353. )
  354. ##################################################################################################################################
  355. # IDEA VIEWS #
  356. ##################################################################################################################################
  357. def new_trial_idea(request):
  358. '''
  359. NOTE: BLANK CANVAS, TRIAL USER IDEA ADDITION
  360. '''
  361. if request.method == 'POST':
  362. print("..")
  363. canvas_pk = request.POST['canvas_pk']
  364. try:
  365. canvas = Canvas.objects.get(pk=canvas_pk)
  366. except Canvas.DoesNotExist:
  367. return HttpResponse('Canvas does not exist', status=404)
  368. logged_in_user = request.user
  369. project = canvas.project
  370. # can't add ideas if the canvas is unavailable or if the blank canvas is being edited to by an authenticated user
  371. if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
  372. return HttpResponse('Unauthorized', status=401)
  373. category = request.POST['idea_category']
  374. idea = Idea(
  375. canvas = canvas,
  376. category = category,
  377. text = '',
  378. title = f'Canvas {canvas_pk} TRIAL IDEA'
  379. )
  380. idea.save()
  381. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  382. return_idea = serialize(
  383. 'json',
  384. [idea],
  385. cls=IdeaEncoder
  386. )
  387. idea.delete()
  388. # singular idea, remove enclosing square brackets
  389. return_idea = return_idea[1:-1]
  390. data = {
  391. 'idea': return_idea,
  392. }
  393. return JsonResponse(data)
  394. def delete_trial_idea(idea_pk):
  395. '''
  396. NOTE: BLANK CANVAS, TRIAL USER IDEA DELETION - IMM
  397. '''
  398. Idea.objects.get(pk=idea_pk).delete()
  399. def new_idea(request):
  400. '''
  401. Creation of a new idea. This gets the id for the canvas in which it is created from the calling URL
  402. '''
  403. if request.method == 'POST':
  404. canvas_pk = request.POST['canvas_pk']
  405. category = request.POST['idea_category']
  406. logged_in_user = request.user
  407. try:
  408. canvas = Canvas.objects.get(pk = canvas_pk)
  409. project = canvas.project
  410. except:
  411. Canvas.DoesNotExist
  412. return HttpResponse('Canvas does not exist', status=404)
  413. Project.DoesNotExist
  414. return HttpResponse('Project does not exist', status=404)
  415. # can't add ideas if the canvas is unavailable or if the blank canvas is being edited to by an authenticated user
  416. if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
  417. return HttpResponse('Unauthorized', status=401)
  418. idea = Idea(
  419. canvas = canvas,
  420. category = category,
  421. text = ''
  422. )
  423. idea.save()
  424. # This is so I can click on it in the django admin - should probably delete later
  425. idea.title = f'Canvas {canvas_pk} Idea {idea.pk}'
  426. idea.save()
  427. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  428. return_idea = serialize(
  429. 'json',
  430. [idea],
  431. cls=IdeaEncoder
  432. )
  433. # singular idea, remove enclosing square brackets
  434. return_idea = return_idea[1:-1]
  435. channel_layer = get_channel_layer()
  436. room_name = canvas_pk + "_idea"
  437. room_group_name = 'canvas_%s' %room_name
  438. data = {
  439. 'function': request.POST['function'],
  440. 'idea': return_idea,
  441. }
  442. async_to_sync(channel_layer.group_send)(
  443. room_group_name,
  444. {
  445. 'type': 'channel_message',
  446. 'data': data
  447. }
  448. )
  449. return HttpResponse(status=200)
  450. def delete_idea(request):
  451. '''
  452. Deletion of an idea
  453. '''
  454. if request.method == 'POST':
  455. try:
  456. logged_in_user = request.user
  457. idea_pk = request.POST['idea_pk']
  458. idea = Idea.objects.get(pk=idea_pk)
  459. canvas = idea.canvas
  460. project = canvas.project
  461. except:
  462. Idea.DoesNotExist
  463. return HttpResponse('Idea does not exist', status=404)
  464. Canvas.DoesNotExist
  465. return HttpResponse('Canvas does not exist', status=404)
  466. Project.DoesNotExist
  467. return HttpResponse('Project does not exist', status=404)
  468. # can't remove ideas if the canvas is unavailable or if the blank canvas is being edited by an authenticated user
  469. if (not user_permission(logged_in_user, project) and ('blank-' not in canvas.title and logged_in_user.is_authenticated)):
  470. return HttpResponse('Unauthorized', status=401)
  471. category = idea.category
  472. # get every tag associated with the idea
  473. tags = idea.tags.all()
  474. removed_tags = []
  475. json_tagged_canvases = []
  476. json_tagged_ideas = []
  477. json_tags = []
  478. # iterate through the tags, removing the idea from each
  479. for tag in tags:
  480. tag.idea_set.remove(idea)
  481. tag.save()
  482. # check if any ideas remain
  483. updated_ideas = tag.idea_set.filter(canvas=canvas).distinct()
  484. # if no idaes remain, remove the canvas from the tag's canvas set as well
  485. if not updated_ideas:
  486. tag.canvas_set.remove(canvas)
  487. tag.save()
  488. canvas.save()
  489. removed_tags.append(tag)
  490. return_tag_data = []
  491. for tag in removed_tags:
  492. json_tagged_canvases=(
  493. serialize(
  494. 'json',
  495. tag.canvas_set.all(),
  496. cls=CanvasEncoder
  497. )
  498. )
  499. json_tagged_ideas=(
  500. serialize(
  501. 'json',
  502. tag.idea_set.all(),
  503. cls=IdeaEncoder
  504. )
  505. )
  506. json_tags=(
  507. serialize(
  508. 'json',
  509. [tag],
  510. cls = CanvasTagEncoder
  511. )
  512. )
  513. json_tags = json_tags[1:-1]
  514. tag_data = {
  515. 'taggedCanvases': json_tagged_canvases,
  516. 'taggedIdeas': json_tagged_ideas,
  517. 'tags': json_tags,
  518. }
  519. return_tag_data.append(tag_data)
  520. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  521. return_idea = serialize(
  522. 'json',
  523. [idea],
  524. cls=IdeaEncoder
  525. )
  526. # singular idea, remove enclosing square brackets
  527. return_idea = return_idea[1:-1]
  528. idea.delete()
  529. data = {
  530. 'function': request.POST['function'],
  531. 'returnTagData': return_tag_data,
  532. 'idea': return_idea,
  533. 'ideaCategory': category,
  534. 'ideaListIndex': request.POST['idea_list_index']
  535. }
  536. channel_layer = get_channel_layer()
  537. room_name = f"{canvas.pk}_idea"
  538. room_group_name = 'canvas_%s' %room_name
  539. async_to_sync(channel_layer.group_send)(
  540. room_group_name,
  541. {
  542. 'type': 'channel_message',
  543. 'data': data
  544. }
  545. )
  546. return HttpResponse(status=200)
  547. def edit_idea(request):
  548. '''
  549. Update of an idea
  550. '''
  551. if request.method == 'POST':
  552. try:
  553. logged_in_user = request.user
  554. idea_pk = request.POST['idea_pk']
  555. input_text = request.POST['input_text']
  556. idea = Idea.objects.get(pk = idea_pk)
  557. canvas = idea.canvas
  558. project = canvas.project
  559. except:
  560. Idea.DoesNotExist
  561. return HttpResponse('Idea does not exist', status=404)
  562. Canvas.DoesNotExist
  563. return HttpResponse('Canvas does not exist', status=404)
  564. Project.DoesNotExist
  565. return HttpResponse('Project does not exist', status=404)
  566. current_tags_in_idea = idea.tags.all()
  567. old_text = idea.text
  568. if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
  569. return HttpResponse('Unauthorized', status=401)
  570. input_text = strip_tags(input_text)
  571. updated_tags_in_idea = []
  572. new_tags = []
  573. new_tags_canvas_set = []
  574. new_tags_idea_set = []
  575. removed_tags = []
  576. removed_tags_canvas_set = []
  577. removed_tags_idea_set = []
  578. # 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
  579. for temp_canvas in project.canvas_set.all():
  580. for tag in temp_canvas.tags.all():
  581. # if the tag is in the input_text, add it. RHS of and operation is to keep the list elements unique
  582. if tag.label in input_text and tag not in updated_tags_in_idea:
  583. updated_tags_in_idea.append(tag)
  584. # if it was in the old text and no longer occurs, remove it
  585. elif tag.label in old_text and tag.label not in input_text:
  586. removed_tags.append(tag)
  587. # update the tags field in canvas by setting them - implicitly remove the removed tags
  588. canvas.tags.set(updated_tags_in_idea)
  589. canvas.save()
  590. # the same for idea's tags field
  591. idea.tags.set(updated_tags_in_idea)
  592. idea.text = input_text
  593. idea.save()
  594. new_return_tag_data = []
  595. removed_return_tag_data = []
  596. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  597. return_idea = serialize(
  598. 'json',
  599. [idea],
  600. cls=IdeaEncoder
  601. )
  602. return_idea = return_idea[1:-1]
  603. for tag in updated_tags_in_idea:
  604. new_tags=(
  605. serialize(
  606. 'json',
  607. [tag],
  608. cls = CanvasTagEncoder
  609. )
  610. )
  611. new_tags = new_tags[1:-1]
  612. new_tags_canvas_set=(
  613. serialize(
  614. 'json',
  615. tag.canvas_set.all(),
  616. cls=CanvasEncoder
  617. )
  618. )
  619. new_tags_idea_set=(
  620. serialize(
  621. 'json',
  622. tag.idea_set.all(),
  623. cls=IdeaEncoder
  624. )
  625. )
  626. tag_data = {
  627. 'newTag': new_tags,
  628. 'newTaggedCanvases': new_tags_canvas_set,
  629. 'newTaggedIdeas': new_tags_idea_set,
  630. }
  631. new_return_tag_data.append(tag_data)
  632. for tag in removed_tags:
  633. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  634. removed_tags=(
  635. serialize(
  636. 'json',
  637. [tag],
  638. cls = CanvasTagEncoder
  639. )
  640. )
  641. removed_tags = removed_tags[1:-1]
  642. removed_tags_canvas_set=(
  643. serialize(
  644. 'json',
  645. tag.canvas_set.all(),
  646. cls=CanvasEncoder
  647. )
  648. )
  649. removed_tags_idea_set=(
  650. serialize(
  651. 'json',
  652. tag.idea_set.all(),
  653. cls=IdeaEncoder
  654. )
  655. )
  656. tag_data = {
  657. 'removedTag': removed_tags,
  658. 'removedTaggedCanvases': removed_tags_canvas_set,
  659. 'removedTaggedIdeas': removed_tags_idea_set,
  660. }
  661. removed_return_tag_data.append(tag_data)
  662. data = {
  663. 'function': request.POST['function'],
  664. 'removedReturnTagData': removed_return_tag_data,
  665. 'newReturnTagData': new_return_tag_data,
  666. 'idea': return_idea,
  667. 'oldText': old_text,
  668. 'ideaCategory': idea.category,
  669. 'ideaListIndex': request.POST['idea_list_index'],
  670. }
  671. channel_layer = get_channel_layer()
  672. room_name = f"{canvas.pk}_idea"
  673. room_group_name = 'canvas_%s' %room_name
  674. async_to_sync(channel_layer.group_send)(
  675. room_group_name,
  676. {
  677. 'type': 'channel_message',
  678. 'data': data
  679. }
  680. )
  681. return HttpResponse(status=200)
  682. ##################################################################################################################################
  683. # COMMENT VIEWS #
  684. ##################################################################################################################################
  685. def new_comment(request):
  686. if request.method == 'POST':
  687. logged_in_user = request.user
  688. idea_pk = request.POST['idea_pk']
  689. input_text = request.POST['input_text']
  690. try:
  691. idea = Idea.objects.get(pk=idea_pk)
  692. canvas = idea.canvas
  693. project = canvas.project
  694. except:
  695. Idea.DoesNotExist
  696. return HttpResponse('Idea does not exist.', status=404)
  697. Canvas.DoesNotExist
  698. return HttpResponse('Canvas does not exist.', status=404)
  699. Project.DoesNotExist
  700. return HttpResponse('Project does not exist.', status=404)
  701. if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
  702. return HttpResponse('Unauthorized', status=401)
  703. text = input_text
  704. text = strip_tags(text)
  705. comment = IdeaComment(
  706. user = logged_in_user,
  707. text = text,
  708. idea = idea
  709. )
  710. comment.save()
  711. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  712. json_comment = serialize(
  713. 'json',
  714. [comment],
  715. cls = IdeaCommentEncoder
  716. )
  717. # singular comment, remove enclosing square brackets
  718. json_comment = json_comment[1:-1]
  719. data = {
  720. 'function': request.POST['function'],
  721. 'comment': json_comment,
  722. 'ideaCategory': idea.category,
  723. 'ideaListIndex': request.POST['idea_list_index']
  724. }
  725. channel_layer = get_channel_layer()
  726. room_name = f"{canvas.pk}_comment"
  727. room_group_name = 'canvas_%s' %room_name
  728. async_to_sync(channel_layer.group_send)(
  729. room_group_name,
  730. {
  731. 'type': 'channel_message',
  732. 'data': data
  733. }
  734. )
  735. return HttpResponse(status=200)
  736. def delete_comment(request):
  737. '''
  738. Deletion of a comment
  739. '''
  740. if request.method == 'POST':
  741. logged_in_user = request.user
  742. comment_pk = request.POST['comment_pk']
  743. try:
  744. comment = IdeaComment.objects.get(pk=comment_pk)
  745. canvas = comment.idea.canvas
  746. project = canvas.project
  747. except:
  748. IdeaComment.DoesNotExist
  749. return HttpResponse('Comment does not exist.', status=404)
  750. Canvas.DoesNotExist
  751. return HttpResponse('Canvas does not exist.', status=404)
  752. Project.DoesNotExist
  753. return HttpResponse('Project does not exist.', status=404)
  754. if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
  755. return HttpResponse('Forbidden.', status=403)
  756. category = comment.idea.category
  757. comment.delete()
  758. data = {
  759. 'function': request.POST['function'],
  760. 'ideaCategory': category,
  761. 'ideaListIndex': request.POST['idea_list_index'],
  762. 'commentListIndex': request.POST['comment_list_index'],
  763. }
  764. channel_layer = get_channel_layer()
  765. room_name = f"{canvas.pk}_comment"
  766. room_group_name = 'canvas_%s' %room_name
  767. async_to_sync(channel_layer.group_send)(
  768. room_group_name,
  769. {
  770. 'type': 'channel_message',
  771. 'data': data
  772. }
  773. )
  774. return HttpResponse(status=200)
  775. def single_comment_resolve(request):
  776. '''
  777. Resolution of a single comment
  778. '''
  779. if request.method == 'POST':
  780. logged_in_user = request.user
  781. comment_pk = request.POST['comment_pk']
  782. try:
  783. comment = IdeaComment.objects.get(pk=comment_pk)
  784. canvas = comment.idea.canvas
  785. project = canvas.project
  786. except:
  787. IdeaComment.DoesNotExist
  788. return HttpResponse('Comment does not exist.', status=404)
  789. Canvas.DoesNotExist
  790. return HttpResponse('Canvas does not exist.', status=404)
  791. Project.DoesNotExist
  792. return HttpResponse('Project does not exist.', status=404)
  793. if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
  794. return HttpResponse('Forbidden.', status=403)
  795. category = comment.idea.category
  796. comment.resolved = True
  797. comment.save()
  798. data = {
  799. 'function': request.POST['function'],
  800. 'ideaCategory': category,
  801. 'ideaListIndex': request.POST['idea_list_index'],
  802. 'commentListIndex': request.POST['comment_list_index'],
  803. }
  804. channel_layer = get_channel_layer()
  805. room_name = f"{canvas.pk}_comment"
  806. room_group_name = 'canvas_%s' %room_name
  807. async_to_sync(channel_layer.group_send)(
  808. room_group_name,
  809. {
  810. 'type': 'channel_message',
  811. 'data': data
  812. }
  813. )
  814. return HttpResponse(status=200)
  815. def all_comment_resolve(request):
  816. '''
  817. Resolution of comments - mark all as resolved
  818. '''
  819. if request.method == 'POST':
  820. logged_in_user = request.user
  821. idea_pk = request.POST['idea_pk']
  822. try:
  823. idea = Idea.objects.get(pk = idea_pk)
  824. canvas = idea.canvas
  825. project = canvas.project
  826. except:
  827. Idea.DoesNotExist
  828. return HttpResponse('Idea does not exist.', status=404)
  829. Canvas.DoesNotExist
  830. return HttpResponse('Canvas does not exist.', status=404)
  831. Project.DoesNotExist
  832. return HttpResponse('Project does not exist.', status=404)
  833. if (not admin_permission(logged_in_user, project) or (project.title == 'blank-project')):
  834. return HttpResponse('Forbidden.', status=403)
  835. IdeaComment.objects.all().filter(idea = idea).update(resolved=True)
  836. data = {
  837. 'function': request.POST['function'],
  838. 'ideaCategory': idea.category,
  839. 'ideaListIndex': request.POST['idea_list_index'],
  840. }
  841. channel_layer = get_channel_layer()
  842. room_name = f"{canvas.pk}_comment"
  843. room_group_name = 'canvas_%s' %room_name
  844. async_to_sync(channel_layer.group_send)(
  845. room_group_name,
  846. {
  847. 'type': 'channel_message',
  848. 'data': data
  849. }
  850. )
  851. return HttpResponse(status=200)
  852. ##################################################################################################################################
  853. # COLLABORATOR AND LANDING PAGE VIEWS #
  854. ##################################################################################################################################
  855. def index(request):
  856. return render(request, 'index.html')
  857. def register(request):
  858. if request.method == 'POST':
  859. form = SignUpForm(request.POST)
  860. if form.is_valid():
  861. username = form.cleaned_data['name']
  862. email = form.cleaned_data['email']
  863. password = form.cleaned_data['password']
  864. newUser = User.objects.create_user(
  865. username = username,
  866. email = email,
  867. password = password
  868. )
  869. return HttpResponseRedirect(reverse('index'))
  870. else:
  871. form = SignUpForm(initial = {
  872. 'name': '',
  873. 'email': '',
  874. 'password': '',
  875. 'password2': '',
  876. })
  877. return render(
  878. request,
  879. 'catalog/register.html',
  880. {'form': form}
  881. )
  882. def add_user(request):
  883. '''
  884. Function for addition of user to project
  885. '''
  886. if request.method == 'POST':
  887. project_pk = request.POST['project_pk']
  888. try:
  889. project = Project.objects.get(pk=project_pk)
  890. except:
  891. Project.DoesNotExist
  892. return HttpResponse('Project does not exist.', status=404)
  893. name = request.POST['name']
  894. logged_in_user = request.user
  895. # check is admin
  896. if (not admin_permission(logged_in_user, project)):
  897. return HttpResponse('Forbidden.', status=403)
  898. else:
  899. try:
  900. user = User.objects.get(username=name)
  901. except:
  902. User.DoesNotExist
  903. return HttpResponse('User does not exist.', status=404)
  904. if user in project.users.all() or user in project.admins.all():
  905. reply = ''
  906. if user is logged_in_user:
  907. reply = 'Error: you\'re already a collaborator, you can\'t add yourself!'
  908. else:
  909. reply = 'Error: ' + name + ' is already a collaborator!'
  910. return HttpResponse(reply, status=500)
  911. project.users.add(user)
  912. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  913. json_user = serialize(
  914. 'json',
  915. [user],
  916. cls = UserModelEncoder
  917. )
  918. # singular user - remove enclosing square brackets
  919. json_user = json_user[1:-1]
  920. data = {
  921. 'function': request.POST['function'],
  922. 'user': json_user,
  923. }
  924. channel_layer = get_channel_layer()
  925. room_name = project_pk + "_collab"
  926. room_group_name = 'project_%s' %room_name
  927. async_to_sync(channel_layer.group_send)(
  928. room_group_name,
  929. {
  930. 'type': 'channel_message',
  931. 'data': data
  932. }
  933. )
  934. return HttpResponse(status=200)
  935. def delete_user(request):
  936. '''
  937. Function for deleting a user from the project.
  938. '''
  939. if request.method == 'POST':
  940. project_pk = request.POST['project_pk']
  941. try:
  942. project = Project.objects.get(pk=project_pk)
  943. except:
  944. Project.DoesNotExist
  945. return HttpResponse('Project does not exist.', status=404)
  946. logged_in_user = request.user
  947. # check is admin
  948. if (not admin_permission(logged_in_user, project)):
  949. return HttpResponse('Forbidden', status=403)
  950. try:
  951. user = User.objects.get(pk=request.POST['user_pk'])
  952. except:
  953. User.DoesNotExist
  954. return HttpResponse('User does not exist.', status=404)
  955. if user not in project.users.all():
  956. reply = 'Error: ' + name + ' is not a collaborator'
  957. return HttpResponse(reply, status=500)
  958. admins = project.admins.all()
  959. # if there is one admin who is the logged-in user, do not allow them to
  960. # delete themselves. It's implied that if there's one admin, the logged_in
  961. # user is that admin, as earlier it is checked that the logged_in user
  962. # is in the project admin set
  963. if (len(admins) == 1 and user in admins):
  964. reply = 'Error: You are the only admin, you may not delete yourself!'
  965. return HttpResponse(reply, status=500)
  966. victim_is_admin = "false"
  967. # if the user is also an admin, remove them from that field also
  968. if user in admins:
  969. victim_is_admin = "true"
  970. project.admins.remove(user)
  971. project.users.remove(user)
  972. data = {
  973. 'function': request.POST['function'],
  974. 'userListIndex': request.POST['user_list_index'],
  975. 'victimIsAdmin': victim_is_admin,
  976. }
  977. channel_layer = get_channel_layer()
  978. room_name = project_pk + "_collab"
  979. room_group_name = 'project_%s' %room_name
  980. async_to_sync(channel_layer.group_send)(
  981. room_group_name,
  982. {
  983. 'type': 'channel_message',
  984. 'data': data
  985. }
  986. )
  987. return HttpResponse(status=200)
  988. def promote_user(request):
  989. '''
  990. Function for promoting a user to admin status
  991. '''
  992. if request.method == 'POST':
  993. project_pk = request.POST['project_pk']
  994. try:
  995. project = Project.objects.get(pk=project_pk)
  996. except:
  997. Project.DoesNotExist
  998. return HttpResponse('Project does not exist', status=404)
  999. logged_in_user = request.user
  1000. # check is admin
  1001. if (not admin_permission(logged_in_user, project)):
  1002. return HttpResponse('Forbidden', status=403)
  1003. try:
  1004. user = User.objects.get(pk=request.POST['user_pk'])
  1005. except:
  1006. User.DoesNotExist
  1007. return HttpResponse('User does not exist', status=404)
  1008. name_str = user.username
  1009. admins = project.admins.all()
  1010. # check presence in admin set
  1011. if user in admins:
  1012. # additionally check the user isn't trying to promote themselves
  1013. if user is logged_in_user:
  1014. name_str = 'you are'
  1015. else:
  1016. name_str = name_str + ' is '
  1017. reply = 'Error: ' + name_str + ' already an admin!'
  1018. return HttpResponse(reply, status=500)
  1019. project.admins.add(user)
  1020. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  1021. json_user = serialize(
  1022. 'json',
  1023. [user],
  1024. cls = UserModelEncoder
  1025. )
  1026. # singular user - remove enclosing square brackets
  1027. json_user = json_user[1:-1]
  1028. data = {
  1029. 'function': request.POST['function'],
  1030. 'admin': json_user,
  1031. }
  1032. channel_layer = get_channel_layer()
  1033. room_name = project_pk + "_collab"
  1034. room_group_name = 'project_%s' %room_name
  1035. async_to_sync(channel_layer.group_send)(
  1036. room_group_name,
  1037. {
  1038. 'type': 'channel_message',
  1039. 'data': data
  1040. }
  1041. )
  1042. return HttpResponse(status=200)
  1043. def demote_user(request):
  1044. '''
  1045. Function to delete a user from the admin field - this is for demotion only.
  1046. For complete deletion, call delete user
  1047. '''
  1048. if request.method == 'POST':
  1049. project_pk = request.POST['project_pk']
  1050. try:
  1051. project = Project.objects.get(pk=project_pk)
  1052. except:
  1053. Project.DoesNotExist
  1054. return HttpResponse('Project does not exist', status=404)
  1055. logged_in_user = request.user
  1056. # check is admin
  1057. if (not admin_permission(logged_in_user, project)):
  1058. return HttpResponse('Forbidden', status=403)
  1059. try:
  1060. user = User.objects.get(pk=request.POST['user_pk'])
  1061. except:
  1062. User.DoesNotExist
  1063. return HttpResponse('User does not exist', status=404)
  1064. admins = project.admins.all()
  1065. # Can't delete a non-existent admin
  1066. if user not in admins:
  1067. reply = 'Error: ' + name + ' is not an admin'
  1068. return HttpResponse(reply, status=500)
  1069. # if there is one admin who is the logged-in user, do not allow them to
  1070. # delete themselves
  1071. if len(admins) == 1:
  1072. reply = 'Error: You are the only admin, you may not demote yourself!'
  1073. return HttpResponse(reply, status=500)
  1074. project.admins.remove(user)
  1075. data = {
  1076. 'function': request.POST['function'],
  1077. 'adminListIndex': request.POST['admin_list_index']
  1078. }
  1079. channel_layer = get_channel_layer()
  1080. room_name = project_pk + "_collab"
  1081. room_group_name = 'project_%s' %room_name
  1082. async_to_sync(channel_layer.group_send)(
  1083. room_group_name,
  1084. {
  1085. 'type': 'channel_message',
  1086. 'data': data
  1087. }
  1088. )
  1089. return HttpResponse(status=200)
  1090. def toggle_public(request):
  1091. if request.method == 'POST':
  1092. project_pk = request.POST['project_pk']
  1093. try:
  1094. project = Project.objects.get(pk=project_pk)
  1095. except:
  1096. Project.DoesNotExist
  1097. return HttpResponse('Project does not exist', status=404)
  1098. logged_in_user = request.user
  1099. # check is admin
  1100. if (not admin_permission(logged_in_user, project)):
  1101. return HttpResponse('Forbidden', status=403)
  1102. project.is_public = not(project.is_public)
  1103. project.save()
  1104. data = {
  1105. 'function': request.POST['function'],
  1106. }
  1107. channel_layer = get_channel_layer()
  1108. room_name = project_pk + "_collab"
  1109. room_group_name = 'project_%s' %room_name
  1110. async_to_sync(channel_layer.group_send)(
  1111. room_group_name,
  1112. {
  1113. 'type': 'channel_message',
  1114. 'data': data
  1115. }
  1116. )
  1117. return HttpResponse(status=200)
  1118. ##################################################################################################################################
  1119. # TAG VIEWS #
  1120. ##################################################################################################################################
  1121. def add_tag(request):
  1122. '''
  1123. ADDITION OF NEW TAG
  1124. '''
  1125. if request.method == 'POST':
  1126. try:
  1127. canvas = Canvas.objects.get(pk=request.POST['canvas_pk'])
  1128. print(canvas)
  1129. project = canvas.project
  1130. except:
  1131. Canvas.DoesNotExist
  1132. return HttpResponse('Canvas does not exist.', status=404)
  1133. Project.DoesNotExist
  1134. return HttpResponse('Project does not exist.', status=404)
  1135. logged_in_user = request.user
  1136. label = request.POST['label']
  1137. if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
  1138. return HttpResponse('Unauthorized', status=401)
  1139. # check existence of tag within project - avoid duplicating tags
  1140. if CanvasTag.objects.filter(label=label, canvas_set__project=project).exists():
  1141. tag = CanvasTag.objects.get(label=label)
  1142. tag_canvas_set = tag.canvas_set.all()
  1143. if canvas not in tag_canvas_set:
  1144. tag.canvas_set.add(canvas)
  1145. else:
  1146. # only create tag if it doesn't exist anywhere visible to the user
  1147. tag = CanvasTag(label=label)
  1148. tag.save()
  1149. tag.canvas_set.add(canvas)
  1150. tag.save()
  1151. canvas.save()
  1152. # check every canvas for presence of new tag's label in those canvases on creation of new tag
  1153. for canvas in project.canvas_set.all():
  1154. ideas = Idea.objects.filter(canvas=canvas)
  1155. for idea in ideas:
  1156. if tag.label in idea.text:
  1157. if idea not in tag.idea_set.all():
  1158. tag.idea_set.add(idea)
  1159. idea.save()
  1160. # skip the below step if the above is false
  1161. if canvas not in tag.canvas_set.all():
  1162. canvas.save()
  1163. tag.canvas_set.add(canvas)
  1164. canvas.save()
  1165. # save tag if modifications made
  1166. tag.save()
  1167. tags = CanvasTag.objects.filter(canvas_set__project=project).distinct()
  1168. json_tagged_canvases = []
  1169. json_tagged_ideas = []
  1170. # for t in tags:
  1171. json_tagged_canvases.append(
  1172. serialize(
  1173. 'json',
  1174. tag.canvas_set.all().order_by('-id'),
  1175. cls=CanvasEncoder
  1176. )
  1177. )
  1178. json_tagged_ideas.append(
  1179. serialize(
  1180. 'json',
  1181. tag.idea_set.all().order_by('canvas'),
  1182. cls=IdeaEncoder
  1183. )
  1184. )
  1185. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  1186. json_tag = serialize(
  1187. 'json',
  1188. [tag],
  1189. cls = CanvasTagEncoder
  1190. )
  1191. # singular tag - remove enclosing square brackets
  1192. json_tag = json_tag[1:-1]
  1193. data = {
  1194. 'function': request.POST['function'],
  1195. 'taggedCanvases': json_tagged_canvases,
  1196. 'taggedIdeas': json_tagged_ideas,
  1197. 'tag': json_tag,
  1198. }
  1199. channel_layer = get_channel_layer()
  1200. room_name = f"{project.pk}_tag"
  1201. room_group_name = 'project_%s' %room_name
  1202. async_to_sync(channel_layer.group_send)(
  1203. room_group_name,
  1204. {
  1205. 'type': 'channel_message',
  1206. 'data': data
  1207. }
  1208. )
  1209. return HttpResponse(status=200)
  1210. def delete_tag(request):
  1211. '''
  1212. DELETION OF TAG
  1213. '''
  1214. if request.method == 'POST':
  1215. try:
  1216. canvas = Canvas.objects.get(pk=request.POST['canvas_pk'])
  1217. project = canvas.project
  1218. except:
  1219. Canvas.DoesNotExist
  1220. return HttpResponse('Canvas does not exist.', status=404)
  1221. Project.DoesNotExist
  1222. return HttpResponse('Project does not exist.', status=404)
  1223. logged_in_user = request.user
  1224. label = request.POST['label']
  1225. if (not user_permission(logged_in_user, project) or (project.title == 'blank-project')):
  1226. return HttpResponse('Unauthorized', status=401)
  1227. try:
  1228. tag = CanvasTag.objects.get(label=label, canvas_set=canvas)
  1229. except:
  1230. CanvasTag.DoesNotExist
  1231. return HttpResponse('Tag does not exist.', status=404)
  1232. CanvasTag.objects.filter(label=label, canvas_set__project=project).delete()
  1233. # delete any tags that aren't attached to a canvas: they are never useful
  1234. CanvasTag.objects.filter(canvas_set=None).delete()
  1235. # TODO: change serialization method from needing to pass a singleton list to accepting a single model instance
  1236. json_tag = serialize(
  1237. 'json',
  1238. [tag],
  1239. cls = CanvasTagEncoder
  1240. )
  1241. # singular tag - remove enclosing square brackets
  1242. json_tag = json_tag[1:-1]
  1243. data = {
  1244. 'function': request.POST['function'],
  1245. 'tag': json_tag,
  1246. }
  1247. channel_layer = get_channel_layer()
  1248. room_name = f"{project.pk}_tag"
  1249. room_group_name = 'project_%s' %room_name
  1250. async_to_sync(channel_layer.group_send)(
  1251. room_group_name,
  1252. {
  1253. 'type': 'channel_message',
  1254. 'data': data
  1255. }
  1256. )
  1257. return HttpResponse(status=200)
  1258. ##################################################################################################################################
  1259. # MISCELLANEOUS FUNCTIONS #
  1260. ##################################################################################################################################
  1261. def search_canvas_for_tag(tag, canvas):
  1262. '''
  1263. check for presence of tag in canvas
  1264. '''
  1265. ideas = Idea.objects.filter(canvas=canvas)
  1266. for idea in ideas:
  1267. if tag.label in idea.text:
  1268. if idea not in tag.idea_set.all():
  1269. tag.idea_set.add(idea)
  1270. # skip the below step if the above is false
  1271. if canvas not in tag.canvas_set.all():
  1272. canvas.save()
  1273. tag.canvas_set.add(canvas)
  1274. # save tag if modifications made
  1275. tag.save()
  1276. def user_permission(logged_in_user, project):
  1277. return ((logged_in_user in project.users.all()) or (project.is_public == True))
  1278. def admin_permission(logged_in_user, project):
  1279. return ((logged_in_user in project.admins.all()) or (project.is_public == True))
  1280. class IdeaEncoder(DjangoJSONEncoder):
  1281. def default(self, obj):
  1282. if isinstance(obj, Idea):
  1283. return str(obj)
  1284. return super().default(obj)
  1285. class IdeaCommentEncoder(DjangoJSONEncoder):
  1286. def default(self, obj):
  1287. if isinstance(obj, IdeaComment):
  1288. return str(obj)
  1289. return super().default(obj)
  1290. class CanvasTagEncoder(DjangoJSONEncoder):
  1291. def default(self, obj):
  1292. if isinstance(obj, CanvasTag):
  1293. return str(obj)
  1294. return super().default(obj)
  1295. class CanvasEncoder(DjangoJSONEncoder):
  1296. def default(self, obj):
  1297. if isinstance(obj, Canvas):
  1298. return str(obj)
  1299. return super().default(obj)
  1300. class ProjectModelEncoder(DjangoJSONEncoder):
  1301. def default(self, obj):
  1302. if isinstance(obj, Project):
  1303. return str(obj)
  1304. return super().default(obj)
  1305. class UserModelEncoder(DjangoJSONEncoder):
  1306. def default(self, obj):
  1307. if isinstance(obj, CanvasTag):
  1308. return str(obj)
  1309. return super().default(obj)
  1310. class Meta:
  1311. model = User
  1312. exclude = ('password',)