from django import forms from django.template.defaultfilters import slugify import django import fastn.django from . import models from .forms import VisibilityField from slides import renderer_utils import subprocess def create_blank_template(request: django.http.HttpRequest): result = subprocess.run(["bash", "../scripts/create-blank-template.sh"]) exit_code = result.returncode if exit_code != 0: return django.http.HttpResponse("Blank template created successfully.") else: err = result.stderr return django.http.HttpResponse( f"Blank Template Creation error: {err}, code: {exit_code}" ) @fastn.django.action class CreatePresentation(fastn.django.Form): title = forms.CharField(max_length=200) template_slug = forms.SlugField() team_slug = forms.SlugField() target_team = forms.SlugField() visibility = VisibilityField() def clean_target_team(self): target_team = self.cleaned_data["target_team"] if self.request.user.is_anonymous: raise forms.ValidationError("User not logged in") try: target_team = models.Org.objects.get(slug=target_team) except models.Org.DoesNotExist: if target_team == self.request.user.username: target_team = models.Org.create( name=self.request.user.username, slug=self.request.user.username, owner=self.request.user, ) else: raise forms.ValidationError("Team does not exist") # TODO: ensure user has write access to the team # tm = models.TeamMember.objects.get( # team=target_team, user=self.request.user # ) # raises 404 if not found # if tm.role == models.TeamMember.ROLE_GUEST: # # Guests can not create presentation, only participate in existing ones # raise django.http.Http404("User is not authenticated") return target_team def clean_team_slug(self): team_slug = self.cleaned_data["team_slug"] template_slug = self.cleaned_data["template_slug"] try: self.template = models.Presentation.objects.get( team__slug=team_slug, slug=template_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Template {team_slug}/{template_slug} does not exist" ) return team_slug def save(self): print("CreatePresentation.save") new_presentation = models.Presentation.objects.create( title=self.cleaned_data["title"], # TODO: the following can raise an exception due to unique constraint, handle it. slug=slugify(self.cleaned_data["title"]), team=self.cleaned_data["target_team"], owner=self.request.user, setting_ftd=self.template.setting_ftd, fastn_ftd=self.template.fastn_ftd, status="draft", is_template=False, visibility=self.cleaned_data["visibility"], ) # Copy slides from the template to the new presentation for template_slide in self.template.slides.order_by("order"): models.Slide.objects.create( title=template_slide.title, slug=template_slide.slug, snapshot=template_slide.snapshot, presentation=new_presentation, order=template_slide.order, content=template_slide.content, status="draft", ) # Create the response data return fastn.django.redirect(new_presentation.presentation_url()) @fastn.django.action class CreateSlide(fastn.django.Form): presentation_slug = forms.SlugField() team_slug = forms.SlugField() template_slide_slug = forms.SlugField() template_presentation_slug = forms.SlugField() def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") if self.request.user.is_anonymous: raise forms.ValidationError("User not logged in") try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} in team {team_slug} does not exist" ) template_slide_slug = self.cleaned_data.get("template_slide_slug") template_presentation_slug = self.cleaned_data.get("template_presentation_slug") try: self.template_slide = models.Slide.objects.get( slug=template_slide_slug, presentation__slug=template_presentation_slug, presentation__is_template=True, ) except models.Slide.DoesNotExist: raise forms.ValidationError( f"Template Slide {template_slide_slug} in " f"presentation {template_presentation_slug}" f"does not exist" ) def next_slide_order(self): current_max_ordered_slide = ( models.Slide.objects.filter(presentation=self.presentation) .order_by("-order") .first() ) order = 1 if current_max_ordered_slide is not None: # At-least one slide exists for this presentation order = current_max_ordered_slide.order + 1 return order def save(self): # TODO: ensure user has write access to the team order = self.next_slide_order() slides_count = models.Slide.objects.filter( presentation=self.presentation ).count() default_title = f"Untitled {slides_count + 1}" new_slide = models.Slide.objects.create( title=default_title, slug=slugify(default_title), snapshot=self.template_slide.snapshot, presentation=self.presentation, order=order, content=self.template_slide.content, ) new_slide.save() return fastn.django.redirect(self.presentation.presentation_url(order)) @fastn.django.action class MoveSlide(fastn.django.Form): presentation_slug = forms.SlugField() team_slug = forms.SlugField() order = forms.IntegerField() # todo: need to change this direction to enum field direction = forms.CharField(max_length=10) def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") if self.request.user.is_anonymous: raise forms.ValidationError("User not logged in") try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} in team {team_slug} does not exist" ) def swap_slide_orders(self, a, b): # todo: need to swap these unique value fields in a better way original_a_order = a.order original_b_order = b.order a.order = -1 a.save() b.order = original_a_order b.save() a.order = original_b_order a.save() def save(self): presentation_slug = self.cleaned_data.get("presentation_slug") direction = self.cleaned_data.get("direction") if direction == "none": # if direction is not specified return django.http.HttpResponse("No slide changes") all_slides = models.Slide.objects.filter( presentation=self.presentation ).order_by("order") slides_count = len(all_slides) order = self.cleaned_data.get("order") # Case 1: # current state -> 1 2 3 4 5 # if corner slides are moved to out of bound direction (just ignore this) # lets say we are trying to move slide 1 to left which makes no sense # but if someone do try it then we should ignore it. if order == 1 and direction == "left": return django.http.HttpResponse( "Cant move leftmost slide to left (no changes)" ) if order == slides_count and direction == "right": return django.http.HttpResponse( "Cant move rightmost slide to right (no changes)" ) current_slide = models.Slide.objects.get( presentation=self.presentation, order=order ) # Case 2: # current state -> 1 2 3 4 5 # let's say we move slide 4 to left # (*if slide left to current slide exists) # final state -> 1 2 4 3 5 if direction == "left": left_slide_order = order - 1 try: left_slide = models.Slide.objects.get( presentation=self.presentation, order=left_slide_order ) self.swap_slide_orders(left_slide, current_slide) except models.Slide.DoesNotExist: raise forms.ValidationError( f"Slide with order {left_slide_order} in presentation {presentation_slug} does not " f"exist" ) # Case 3: # current state -> 1 2 3 4 5 # let's say we move slide 4 to right # (*if slide right to current slide exists) # final state -> 1 2 3 5 4 if direction == "right": right_slide_order = order + 1 try: right_slide = models.Slide.objects.get( presentation=self.presentation, order=right_slide_order ) self.swap_slide_orders(right_slide, current_slide) except models.Slide.DoesNotExist: raise forms.ValidationError( f"Slide with order {right_slide_order} in presentation {presentation_slug} does not " f"exist" ) # todo: remain in the current slide page # if the current slide order is affected # currently reloading the page return fastn.django.reload() @fastn.django.action class DeleteSlide(fastn.django.Form): presentation_slug = forms.SlugField() team_slug = forms.SlugField() order = forms.IntegerField() def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") if self.request.user.is_anonymous: raise forms.ValidationError("User not logged in") try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} does not exist" ) def save(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") slide_order = self.cleaned_data.get("order") all_slides = models.Slide.objects.filter( presentation=self.presentation ).order_by("order") is_slide_deleted = False # Lesser than or just after the deleted one existing_slide_order = None for s in all_slides: current_order = s.order if current_order == slide_order: render_id = s.ekey # Delete slide object s.delete() # Delete rendered content for this slide (if exists) renderer_utils.delete_rendered_content(render_id) is_slide_deleted = True if current_order > slide_order and is_slide_deleted: s.order = current_order - 1 s.save() all_slides = models.Slide.objects.filter( presentation=self.presentation ).order_by("order") for s in all_slides: current_slide_order = s.order if existing_slide_order is None: existing_slide_order = current_slide_order if current_slide_order <= slide_order: existing_slide_order = current_slide_order # If no slide found after deleting then redirect to blank slide 0 if existing_slide_order is None: existing_slide_order = 0 return fastn.django.redirect( f"/p/{team_slug}/{presentation_slug}/{existing_slide_order}" ) @fastn.django.action class SaveSlide(fastn.django.Form): team_slug = forms.SlugField() presentation_slug = forms.SlugField() order = forms.IntegerField() content = forms.CharField() def clean_content(self): try: self.presentation = models.Presentation.objects.get( team__slug=self.cleaned_data["team_slug"], slug=self.cleaned_data["presentation_slug"], ) except models.Presentation.DoesNotExist: raise forms.ValidationError("Presentation does not exist") # TODO: check if user has write permission to this presentation try: self.slide = models.Slide.objects.get( presentation=self.presentation, order=self.cleaned_data["order"], ) except models.Slide.DoesNotExist: raise forms.ValidationError("Slide does not exist") content = self.cleaned_data["content"] self.render(content) # todo: use fastn build to see if the content is valid return content def render(self, content): from slides import renderer_utils fastn_ftd = self.presentation.fastn_ftd setting_ftd = self.presentation.setting_ftd # render and updates self.slide_preview and self.slide_thumbnail # todo: send slide ekey as render_id self.render_success, snapshot_or_error_bytes = renderer_utils.render( "bsj76t3ev8S", fastn_ftd, setting_ftd, content ) if self.render_success: print("RENDER SUCCESS") self.snapshot_bytes = snapshot_or_error_bytes else: print("RENDER FAILURE") self.error_bytes = snapshot_or_error_bytes def save(self): print(f"Render status: {self.render_success}") if not self.render_success: error_response = {} if self.error_bytes is not None: print("Error bytes found") error_response["errors"] = { "content": self.error_bytes.decode("utf-8"), } else: print("No error bytes found") return django.http.JsonResponse(error_response, status=200) # all data is valid, only save updates database # Save slide content self.slide.content = self.cleaned_data["content"] # TODO: before first save, the self.snapshot is the shared version, post that we have to create # a new snapshot for this slide. Lets add .cloned boolean field in Snapshot table. snapshot = models.SlideSnapshot.objects.create( preview=self.snapshot_bytes, thumbnail=self.snapshot_bytes ) self.slide.snapshot = snapshot self.slide.save() return fastn.django.reload() @fastn.django.action class ToggleTemplate(fastn.django.Form): team_slug = forms.SlugField() presentation_slug = forms.SlugField() def clean_team_slug(self): team_slug = self.cleaned_data.get("team_slug") try: self.team = models.Org.objects.get( slug=team_slug, ) except models.Org.DoesNotExist: raise forms.ValidationError(f"Team {team_slug} does not exist") return team_slug def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} for team {team_slug} does not exist" ) def save(self): self.presentation.is_template = not self.presentation.is_template self.presentation.save() return fastn.django.reload() @fastn.django.action class SavePresentationSettings(fastn.django.Form): team_slug = forms.SlugField() presentation_slug = forms.SlugField() fastn_conf = forms.CharField() settings_conf = forms.CharField() def clean_team_slug(self): team_slug = self.cleaned_data.get("team_slug") try: self.team = models.Org.objects.get( slug=team_slug, ) except models.Org.DoesNotExist: raise forms.ValidationError(f"Team {team_slug} does not exist") return team_slug def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") # todo: there is no check for fastn and settings data yet # will be validating it later try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} for team {team_slug} does not exist" ) def save(self): self.presentation.fastn_ftd = self.cleaned_data.get("fastn_conf") self.presentation.setting_ftd = self.cleaned_data.get("settings_conf") self.presentation.save() return fastn.django.reload() @fastn.django.action class ChangePresentationTitle(fastn.django.Form): team_slug = forms.SlugField() presentation_slug = forms.SlugField() title = forms.CharField() def clean_team_slug(self): team_slug = self.cleaned_data.get("team_slug") try: self.team = models.Org.objects.get( slug=team_slug, ) except models.Org.DoesNotExist: raise forms.ValidationError(f"Team {team_slug} does not exist") return team_slug def clean(self): presentation_slug = self.cleaned_data.get("presentation_slug") team_slug = self.cleaned_data.get("team_slug") team_name = self.team.name self.new_presentation_title = self.cleaned_data.get("title") self.new_presentation_slug = slugify(self.new_presentation_title) if len(self.new_presentation_title) == 0: empty_title_error_response = { "errors": { "title": "Presentation Title is empty", } } self.error = empty_title_error_response return try: self.presentation = models.Presentation.objects.get( slug=self.new_presentation_slug, team__slug=team_slug ) duplicate_presentation_error_response = { "errors": { "title": f"Presentation with this title " f"already exists for the " f"current team {team_name}. Choose a different title", } } self.error = duplicate_presentation_error_response return except models.Presentation.DoesNotExist: try: self.presentation = models.Presentation.objects.get( slug=presentation_slug, team__slug=team_slug ) except models.Presentation.DoesNotExist: raise forms.ValidationError( f"Presentation {presentation_slug} for team {team_slug} does not exist" ) def save(self): if getattr(self, "error", None) is not None: return django.http.JsonResponse(self.error, status=200) team_slug = self.cleaned_data.get("team_slug") self.presentation.title = self.new_presentation_title self.presentation.slug = self.new_presentation_slug self.presentation.save() return fastn.django.redirect( f"/p/{team_slug}/{self.new_presentation_slug}/settings/" )