Skip to content

Instantly share code, notes, and snippets.

@amitu
Last active February 7, 2024 11:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save amitu/a1ba8b61cb9359f618d39d84c5f6b5ce to your computer and use it in GitHub Desktop.
Save amitu/a1ba8b61cb9359f618d39d84c5f6b5ce to your computer and use it in GitHub Desktop.
slides dj stuff
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/"
)
from django import forms
from slides import models
class VisibilityField(forms.CharField):
def validate(self, value):
if value not in [
models.VISIBILITY_EVERYONE,
models.VISIBILITY_TEAM,
models.VISIBILITY_ONLY_ME,
]:
raise forms.ValidationError(
f"Invalid visibility value, {value}, "
f"allowed values: {models.VISIBILITY_EVERYONE}, {models.VISIBILITY_TEAM}, {models.VISIBILITY_ONLY_ME}"
)
from django.db import models
from ft.models import User, Org
from encrypted_id.models import EncryptedIDModel
from slides import utils
VISIBILITY_EVERYONE = "everyone"
VISIBILITY_ORG = "org"
VISIBILITY_ONLY_ME = "only_me"
class SlideSnapshot(models.Model):
preview = models.BinaryField() # this is the PNG content
thumbnail = models.BinaryField() # this is the PNG content
class Meta:
db_table = "slides_slide_snapshot"
class OrgTemplate(models.Model):
org = models.ForeignKey(Org, on_delete=models.PROTECT, related_name="+")
template = models.ForeignKey(
"Presentation", on_delete=models.PROTECT, related_name="+"
)
custom_data = models.JSONField(default=dict)
# Needs Org, User
class Presentation(EncryptedIDModel):
# template = models.ForeignKey("Presentation", on_delete=models.PROTECT, related_name="+")
# `snapshot` is redundant, but it's a performance optimization
# snapshot = models.ForeignKey(SlideSnapshot, on_delete=models.PROTECT, related_name="+")
title = models.TextField(max_length=200)
slug = models.SlugField(max_length=200)
org = models.ForeignKey(Org, on_delete=models.PROTECT, related_name="+")
owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name="+")
# we will store a `settings.ftd` file in the database, it will be auto imported in each slide, and can define
# color scheme and other global data.
setting_ftd = models.TextField() # FTD
fastn_ftd = models.TextField() # FTD
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
STATUS_CHOICES = [
("draft", "Draft"),
("approved", "Approved"),
]
status = models.TextField(max_length=10, choices=STATUS_CHOICES, default="draft")
issue_count = models.IntegerField(default=0) # can be calculated from comments
is_template = models.BooleanField(default=False)
# only me, with Org, with everyone
VISIBILITY_CHOICES = [
(VISIBILITY_ONLY_ME, "Only Me"),
(VISIBILITY_ORG, "Org"),
(VISIBILITY_EVERYONE, "Everyone"),
]
visibility = models.TextField(
max_length=10, choices=VISIBILITY_CHOICES, default=VISIBILITY_ORG
)
class Meta:
db_table = "slides_presentation"
unique_together = [("org", "slug")]
def dashboard_view(self):
return {
"title": self.title,
"thumbnail": self.thumbnail_url(),
"updated-on": "Edited 18 min ago", # TODO: calculate this
"url": self.presentation_url(),
}
def thumbnail_url(self):
return self.slides.get(order=1).thumbnail_url()
def preview_url(self):
return self.slides.get(order=1).preview_url()
def to_public_presentation(self):
# record definition in templates.ftd
return {
"id": self.id,
"url": self.template_url(),
"title": self.title,
"org": self.org.name,
"owner_name": self.owner.username,
"thumbnail": self.thumbnail_url(),
"owner_avatar": "/-/ui.fifthtry.com/slides/assets/avatar.svg",
}
def to_detail_presentation(self):
# record definition in template.ftd
"""
:return:
caption template-name: x
string template-slug: x
integer id: x
string owner-name: x
string org-name: x
string org-slug: x
string org-initials: x
string preview: x
string thumbnail: x
string org-avatar: x?
string list slides: x
"""
return {
"id": self.id,
"template-name": self.title,
"org-name": self.org.name,
"owner-name": self.owner.username,
"owner-initials": utils.get_initials(self.owner.username),
"org-slug": self.org.slug,
"org-initials": utils.get_initials(self.org.name),
"template-slug": self.slug,
"preview": self.preview_url(),
"thumbnail": self.thumbnail_url(),
"org-avatar": "/-/ui.fifthtry.com/slides/assets/avatar.svg",
"slides": [s.thumbnail_url() for s in self.slides.all()],
}
def get_slide_by_order(self, order):
return Slide.objects.get(presentation=self, order=order)
def __str__(self) -> str:
return "Id: " + str(self.id) + " " + self.title
def template_url(self) -> str:
return f"/t/{self.org.slug}/{self.slug}/"
def presentation_url(self, order=1) -> str:
if order == 1:
return f"/p/{self.org.slug}/{self.slug}/"
else:
return f"/p/{self.org.slug}/{self.slug}/{order}/"
def settings_url(self) -> str:
return f"/p/{self.org.slug}/{self.slug}/settings/"
def present_url(self) -> str:
return f"/p/{self.org.slug}/{self.slug}/present/"
class RawAsset(models.Model):
content = models.BinaryField() # this is the PNG content
dark_content = models.BinaryField() # this is the PNG content
class Meta:
db_table = "slides_raw_asset"
class Asset(models.Model):
presentation = models.ForeignKey(
Presentation, on_delete=models.PROTECT, related_name="assets"
)
# org = models.ForeignKey(Org, on_delete=models.PROTECT, related_name="+")
name = models.TextField(max_length=200) # this will be the root relative file name
# we will store the dark version
raw = models.ForeignKey(RawAsset, on_delete=models.PROTECT, related_name="+")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
visibility = models.TextField(
max_length=10, choices=Presentation.VISIBILITY_CHOICES, default=VISIBILITY_ORG
)
class Meta:
db_table = "slides_asset"
def url(self):
return "/asset/%s.png" % self.ekey
class Slide(EncryptedIDModel):
# template = models.ForeignKey("Slide", on_delete=models.PROTECT, related_name="+")
# this is the last successful snapshot, in case of error we will not update this.
snapshot = models.ForeignKey(
SlideSnapshot, on_delete=models.PROTECT, related_name="snapshots"
)
# this will be template presentation for slide templates
presentation = models.ForeignKey(
Presentation, on_delete=models.PROTECT, related_name="slides"
)
title = models.TextField(max_length=200)
slug = models.SlugField(max_length=200)
order = models.IntegerField()
content = models.TextField() # FTD
"""
-- import: foo
-- foo.slide-1: Hello
This is a slide.
"""
# if there is any error in the content we will store it here.
error = models.TextField(null=True)
STATUS_CHOICES = [
("draft", "Draft"),
("approved", "Approved"),
]
status = models.TextField(max_length=10, choices=STATUS_CHOICES, default="draft")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
issue_count = models.IntegerField(default=0) # can be calculated from comments
class Meta:
db_table = "slides_slide"
unique_together = [("presentation", "order")]
def to_detail_slide(self):
return {
"content": self.content,
"preview-url": self.preview_url(),
# todo: "is-valid": not self.error,
"is-valid": True,
}
def to_slide_thumbnail(self):
return {
"url": self.presentation.presentation_url(self.order),
"thumbnail-url": self.thumbnail_url(),
# todo: "is-valid": not self.error,
"is-valid": True,
}
def to_template_slide_data(self):
return {
"title": self.title,
"slug": self.slug,
"presentation-slug": self.presentation.slug,
"thumbnail": self.thumbnail_url(),
}
def preview_url(self):
# todo
# return "/preview/%s.png" % self.ekey
return f"/page/serve-image/?slide={self.id}&preview=true"
def thumbnail_url(self):
return f"/page/serve-image/?slide={self.id}&preview=false"
def to_public_slide_preview(self, current_slide):
# string ekey:
# string order:
# boolean is-current:
# string preview-url:
# slide-status status:
return {
"ekey": self.ekey,
"order": self.order,
"is-current": self.order == current_slide,
"preview-url": self.preview_url(),
"status": self.status,
}
def to_public_slide(self, ekey, presentation):
return {
"ekey": ekey,
"presentation": presentation.to_public_presentation(),
}
def url(self):
self.presentation.url() + f"/{self.order}/"
def __str__(self):
return (
"Id: "
+ str(self.id)
+ " "
+ self.presentation.title
+ ": "
+ str(self.order)
)
class Comment(models.Model):
slide = models.ForeignKey(Slide, on_delete=models.PROTECT, related_name="comments")
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name="comments")
content = models.TextField()
issue = models.BooleanField(default=False)
resolved = models.BooleanField(default=False)
position_x = models.IntegerField(null=True)
position_y = models.IntegerField(null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "slides_comment"
class CommentResolution(models.Model):
comment = models.ForeignKey(
Comment, on_delete=models.PROTECT, related_name="resolutions"
)
user = models.ForeignKey(User, on_delete=models.PROTECT, related_name="resolutions")
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
db_table = "slides_comment_resolution"
import django.http
from slides import models
def serve_image(request: django.http.HttpRequest):
# /serve-image/?slide=<id>&preview=true
# slide = id of the slide
# from . import models
slide_id = request.GET["slide"]
is_preview = request.GET["preview"] == "true"
slide = models.Slide.objects.get(id=slide_id)
snapshot = slide.snapshot
data = snapshot.preview if is_preview else snapshot.thumbnail
response = django.http.HttpResponse(data, content_type="image/png")
response["Content-Security-Policy"] = "frame-ancestors http://127.0.0.1:8000"
return response
-- import: ui.fifthtry.com/pages/slides/presentation
-- import: fifthtry.com/user-data
-- import: fifthtry.com/actions/create-slide
-- import: fifthtry.com/actions/delete-slide
-- import: fifthtry.com/actions/save-slide
-- import: fifthtry.com/actions/move-slide
-- import: fastn/processors as pr
-- integer order: 1
$processor$: pr.request-data
-- string org-slug:
$processor$: pr.request-data
-- string presentation-slug:
$processor$: pr.request-data
-- presentation.page:
user-data: user-data
data: $data
org-slug: $org-slug
order: $order
presentation-slug: $presentation-slug
create-slide: create-slide
delete-slide: delete-slide
save-slide: save-slide
move-slide: move-slide
-- presentation.presentation-data data:
$processor$: pr.http
url: http://127.0.0.1:7999/view/presentation/
org-slug: $org-slug
presentation-slug: $presentation-slug
order: $order
from django.urls import path
from . import old_views
from slides import pages
from . import views
from . import actions
# These are slides url
urlpatterns = [
path("action/create-blank-template/", actions.create_blank_template),
path("view/get-public-templates/", views.get_public_templates),
path("view/get-public-template/", views.get_public_template),
path("page/serve-image/", pages.serve_image),
path("action/create-presentation/", actions.CreatePresentation),
path("view/presentation/", views.presentation),
path("view/dashboard/", views.dashboard),
path("view/presentation-settings/", views.presentation_settings),
path("view/present/", views.present),
path("action/save-slide/", actions.SaveSlide),
# Creating new presentation from a template
# Creating new slide at the end
path("action/create-slide/", actions.CreateSlide),
# Delete slide by order
path("action/delete-slide/", actions.DeleteSlide),
# Reorder slide by direction
path("action/move-slide/", actions.MoveSlide),
path("action/toggle-template/", actions.ToggleTemplate),
path("action/save-presentation-settings/", actions.SavePresentationSettings),
path("action/change-presentation-title/", actions.ChangePresentationTitle),
# Present slide
# These are template related stuff
# OLD STUFF ============================================================
path("template/<int:template_id>/", old_views.get_template_by_id),
]
# These are renderer url
urlpatterns += [
path("iframe/rerender/", old_views.rerender),
path("iframe/<slug:folder_name>/", old_views.serve_index_file),
path("iframe/<slug:folder_name>/<path:filename>/", old_views.serve_file),
]
import django.http
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_GET
from django.template.defaultfilters import slugify
import fastn.django
from . import models
from . import utils
# docs: /dev/pages/presentation/
# request: django.http.HttpRequest
# test example:
# http://127.0.0.1:7999/view/presentation/?org-slug=fifthtry&presentation-slug=blank-template&order=1
@csrf_exempt
@require_GET
def presentation(request: django.http.HttpRequest):
org_slug = request.GET["org-slug"]
presentation_slug = request.GET["presentation-slug"]
order_s: str = request.GET["order"]
try:
order: int = int(order_s)
except (ValueError, TypeError):
return django.http.HttpResponseNotFound(
f"order parameter must be an integer value, " f"found {order_s}"
)
presentation = models.Presentation.objects.get_or_404(
org__slug=org_slug, slug=presentation_slug
)
current_slide = presentation.slides.get_or_404(order=order)
# presentation-setting-url: /slides/scenarios/fastn-presentation-logged-in/
# present-url: /slides/scenarios/fastn-presentation-logged-in/
# slide-templates: $slide-templates
# thumbnails: $slide-thumbnails
# todo: extract slide templates on visibility criteria
# based on user's privileges
template_slides = models.Slide.objects.filter(presentation__is_template=True)
slide_template_data = [s.to_template_slide_data() for s in template_slides]
response = {
"title": presentation.title,
"url": presentation.presentation_url(),
"is-valid": True,
"settings-url": presentation.settings_url(),
"present-url": presentation.present_url(),
"slide": current_slide.to_detail_slide(),
"thumbnails": [
s.to_slide_thumbnail() for s in presentation.slides.all().order_by("order")
],
"slide-templates": slide_template_data, # TODO
"comments": [], # TODO
}
return django.http.JsonResponse(response, status=200)
@csrf_exempt
@require_GET
def present(request: django.http.HttpRequest):
presentation_slug = request.GET["presentation-slug"]
org_slug = request.GET["org-slug"]
slide_order = 1
try:
current_presentation = models.Presentation.objects.get(
slug=presentation_slug, org__slug=org_slug
)
except models.Presentation.DoesNotExist:
return django.http.HttpResponseNotFound(
f"Presentation {presentation_slug} for org {org_slug} not found"
)
all_slides = models.Slide.objects.filter(
presentation=current_presentation
).order_by("order")
all_slide_previews = [s.preview_url() for s in all_slides]
response = {
"all-slide-previews": all_slide_previews,
"current-slide-order": slide_order,
"editor-url": f"p/{org_slug}/{presentation_slug}/",
}
return django.http.JsonResponse(response, status=200)
# Test
# http://127.0.0.1:7999/api/settings/?presentation_slug=blank-template
def presentation_settings(request: django.http.HttpRequest):
"""
method: get
expects query param: presentation_slug, org_slug
:return:
{
settings-conf: string,
fastn-conf: string,
is-template: boolean,
editor-url: string,
presentation-title: string,
}
"""
presentation_slug = request.GET["presentation-slug"]
org_slug = request.GET["org-slug"]
try:
current_org = models.Org.objects.get(slug=org_slug)
except models.Org.DoesNotExist:
return django.http.HttpResponseNotFound(f"Team {org_slug} not found")
try:
current_presentation = models.Presentation.objects.get(
slug=presentation_slug, org__slug=org_slug
)
except models.Presentation.DoesNotExist:
return django.http.HttpResponseNotFound(
f"Presentation {presentation_slug} for org {org_slug} not found"
)
response = {
"presentation-title": current_presentation.title,
"org-name": current_org.name,
"fastn-conf": current_presentation.fastn_ftd,
"settings-conf": current_presentation.setting_ftd,
"is-template": current_presentation.is_template,
"editor-url": f"p/{org_slug}/{presentation_slug}/",
}
return django.http.JsonResponse(response, status=200)
def get_public_template(request: django.http.HttpRequest):
org_slug = request.GET["org_slug"]
template_slug = request.GET["template_slug"]
response = models.Presentation.objects.get(
org__slug=org_slug, slug=template_slug
).to_detail_presentation()
response["username"] = request.user.username
return django.http.JsonResponse(response)
def get_public_templates(_request: django.http.HttpRequest):
presentations = models.Presentation.objects.filter(
is_template=True,
visibility=models.VISIBILITY_EVERYONE,
)
template_data = []
for presentation in presentations:
template_data.append(presentation.to_public_presentation())
return django.http.JsonResponse({"templates": template_data})
# dashboard expects:
#
# -- record dashboard-data:
# presentation list presentations:
# template list templates:
#
# -- record presentation:
# caption title:
# string url:
# string thumbnail:
# string updated-on:
#
#
# -- record template:
# caption title:
# string url:
# string thumbnail:
def dashboard(request: django.http.HttpRequest):
org_slug = request.GET.get("org-slug") or request.user.username
# org_slug = request.GET.get("org-slug", request.user.username)
username = request.user.username
current_user, _ = models.User.objects.get_or_create(username=username)
current_org, _ = models.Org.objects.get_or_create(
owner=current_user, defaults={"name": username, "slug": slugify(username)}
)
org_name = current_org.name
toc_links = [
{"title": "Dashboard", "link": f"/t/{org_slug}/"},
{"title": "Templates", "link": "/community/templates/"},
]
# TODO: implement pagination
return django.http.JsonResponse(
{
"toc-links": toc_links,
"org-name": org_name,
"org-slug": org_slug,
"presentations": [
p.dashboard_view()
for p in models.Presentation.objects.filter(org__slug=org_slug)[:10]
],
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment