Compare commits

...

7 Commits

Author SHA1 Message Date
7874151f32 slight bugfix for /edit_quest 2018-10-02 13:52:39 -04:00
423acc7077 user can add to tags now 2018-10-02 13:31:58 -04:00
452d6f22be merged /create_quest into /quest/new 2018-10-02 12:38:17 -04:00
172200eea3 added tags quest homepage 2018-10-02 12:37:33 -04:00
4f70d10ea8 basic search added 2018-10-02 11:35:39 -04:00
7a2f74c86e added quest homepage 2018-10-01 08:35:24 -04:00
edf5ef62b7 padToTwo() works on negative numbers 2018-10-01 07:59:18 -04:00
29 changed files with 298 additions and 87 deletions

View File

@ -5,7 +5,7 @@ By popular demand, I'm building a better anonkun. It doesn't do much right now t
Python 3.6+
PostgreSQL 10.4+
Redis 4.0.10+
Python packages: `django psycopg2 channels channels_redis jinja2 argon2-cffi bleach requests python-magic`
Python packages: `django psycopg2 channels channels_redis jinja2 argon2-cffi bleach requests python-magic django-taggit`
## Install
```
@ -17,6 +17,9 @@ postgres=# ALTER ROLE "titivillus" SET default_transaction_isolation TO 'read co
postgres=# ALTER ROLE "titivillus" SET timezone TO 'UTC';
postgres=# GRANT ALL PRIVILEGES ON DATABASE "titivillus" TO "titivillus";
postgres=# \q
$ psql titivillus
titivillus=# CREATE EXTENSION unaccent;
titivillus=# \q
```
1. Get on the floor
2. Walk the dinosaur

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class CreateQuestConfig(AppConfig):
name = 'create_quest'

View File

@ -1,24 +0,0 @@
#!/usr/bin/env python3
"""
Form(s) for the create_quest page.
"""
from django import forms
from quest.models import Quest, Post
class QuestForm(forms.ModelForm):
"""
The main create_quest form.
"""
class Meta:
model = Quest
fields = ('title',)
class PostForm(forms.ModelForm):
"""
The form for beginning the first post of the quest.
"""
class Meta:
model = Post
fields = ('post_text',)

View File

@ -1,37 +0,0 @@
#!/usr/bin/env python3
"""
/create_quest app views.
"""
from django.contrib import messages
from django.shortcuts import redirect, render
from .forms import QuestForm, PostForm
from quest.models import Quest, Post, Page
def index(request):
"""
The index page for creating new quests.
"""
if request.method == 'POST':
# TODO: clean the post body
quest = Quest(owner=request.user)
quest_form = QuestForm(request.POST, instance=quest)
post = Post(post_type='text')
post_form = PostForm(request.POST, instance=post)
if all((quest_form.is_valid(), post_form.is_valid())):
quest.save()
page = Page(
quest=quest,
page_num=1,
title="Page 1"
)
page.save()
post.quest = quest
post.page = page
post.save()
return redirect('quest:quest', quest_id=quest.id)
else:
quest_form = QuestForm()
post_form = PostForm()
context = {'quest_form': quest_form, 'post_form': post_form}
return render(request, 'create_quest/index.html', context)

View File

@ -2,9 +2,15 @@
{% block title %}Index{% endblock %}
{% block content %}
<h1>Quests 'n Shiet</h1>
<form method="get" action="{{ url('search:index') }}">
<input type="text" name="title" placeholder="Search">
<input type="submit">
</form>
<a href="{{ url('search:index') }}">Advanced</a><br>
<br>
<a href="./quest/1">Unga Bunga Quest</a><br />
{% if request.user.is_authenticated %}
<a href="{{ url('create_quest:index') }}">Create New Quest</a><br />
<a href="{{ url('quest:new_quest') }}">Create New Quest</a><br />
<a href="{{ url('logout:index') }}">Logout</a><br />
{% else %}
<a href="{{ url('signup:index') }}">Sign up</a><br />

View File

@ -19,7 +19,7 @@
<span><a href="{{ url('homepage:index') }}">Home</a></span>
{% block header %}{% endblock %}
</div>
<div id="headerHidden" class="header" style="{% if request.session.get("hide_header") == True %}display:initial;{% else %}display:none;{% endif %}">
<div id="headerHidden" class="header" style="{% if request.session.get("hide_header") == True %}display:flex;{% else %}display:none;{% endif %}">
<span><a onclick="toggle_header();" href="javascript:void(0);"></a></span>
</div>
<ul id="alerts">

View File

@ -4,6 +4,8 @@ Form(s) for the quest page.
"""
from django import forms
from .models import Quest, Post
class DiceCallForm(forms.Form):
"""
The form for the QM making dice calls.
@ -46,3 +48,21 @@ class EditQuestForm(forms.Form):
live_date = forms.DateField(required=False)
live_time = forms.TimeField(required=False)
timezone = forms.IntegerField()
class QuestForm(forms.ModelForm):
"""
The main new quest form.
"""
class Meta:
model = Quest
fields = ('title',)
class PostForm(forms.ModelForm):
"""
The form for beginning the first post of the quest.
"""
class Meta:
model = Post
fields = ('post_text',)

View File

@ -28,8 +28,8 @@
</tr>
<tr>
<td>Live time:</td>
<td><input type="date" name="live_date" id="live_date" value="{{ localtime(quest.live_time).strftime('%Y-%m-%d') }}"></td>
<td><input type="time" name="live_time" id="live_time" step="60" value="{{ localtime(quest.live_time).strftime('%H:%M:%S') }}"></td>
<td><input type="date" name="live_date" id="live_date" value="{% if quest.live_time %}{{ localtime(quest.live_time).strftime('%Y-%m-%d') }}{% endif %}"></td>
<td><input type="time" name="live_time" id="live_time" step="60" value="{% if quest.live_time %}{{ localtime(quest.live_time).strftime('%H:%M:%S') }}{% endif %}"></td>
</tr>
</table>
<input type="hidden" name="timezone" id="timezone">

View File

@ -2,7 +2,7 @@
{% block title %}Start a new quest{% endblock %}
{% block content %}
<h1>New Quest</h1>
<form method="post" action="{{ url('create_quest:index') }}">
<form method="post" action="{{ url('quest:new_quest') }}">
{{ csrf_input }}
{#
<input type="text" placeholder="Quest Title" name="quest_title" maxlength="300" required/><br/>

View File

@ -0,0 +1,99 @@
{% extends "base.html" %}
{% block title %}{{ quest.title }}{% endblock %}
{% block head %}
<link rel="stylesheet" type="text/css" href="{{ static('quest.css') }}">
{% endblock %}
{% block header %}
{% if request.user == quest.owner %}
<span><a href="{{ url('quest:edit_quest', args=[quest_id, page_num]) }}">Edit Quest</a></span>
<script>
const quest_id = {{ quest.id }};
const page_num = '{{ page_num }}';
const SCRIPT_NAME = '{{ request.META["SCRIPT_NAME"] }}';
const anon_name = '{{ quest.anon_name }}';
</script>
<script type="text/javascript" src="{{ static('quest.js') }}"></script>
<script>window.onload = load;</script>
{% endif %}
<span>
<select onChange="window.location.href=this.value">
<optgroup label="Pages">
{% for page in pages %}
<option value="{{ url('quest:quest', args=[quest_id, page.page_num]) }}"{% if page.page_num == page_num %} selected="yes" {% if vars.update({'next_page': loop.nextitem}) %}{% endif %}{% endif %}>{{ page.title }}</option>
{% endfor %}
</optgroup>
{% if appendices %}
<optgroup label="Appendices">
{% for appendix in appendices %}
<option value="{{ url('quest:quest', args=[quest_id, appendix.page_num]) }}"{% if appendix.page_num == page_num %} selected="yes"{% endif %}>{{ appendix.title }}</option>
{% endfor %}
</optgroup>
{% endif %}
</select>
</span>
{% if quest.live %}
<span id="live">
LIVE
</span>
{% else %}
{% if quest.live_time %}
<span id="liveIn">
Live in: <span id="liveCountdown"></span> (<span id="liveTime">{{ localtime(quest.live_time).strftime('%Y-%m-%d %H:%M') }}</span>)
</span>
{% endif %}
{% endif %}
<span id="toggleChat"><a onclick="toggle_chat()" href="javascript:void(0);">{% if request.session.get("hide_chat") == True %}←{% else %}→{% endif %}</a></span>
{% endblock %}
{% block content %}
<div id="questPane" style="width:{% if request.session.get("hide_chat") == True %}100%{% else %}70%{% endif %};">
<center><h1>{{ quest.title }}</h1></center>
Tags: {% for tag in quest.tags.names() %}<a href="{{ url('search:index') + '?tags=' + tag }}">{{ tag }}</a>{% if not loop.last %}, {% endif %}{% endfor %}
{% if request.user == quest.owner %}
<form method="post" action="{{ url('quest:new_tag', kwargs={'quest_id': quest_id}) }}">
{{ csrf_input }}
<input type="text" name="tag">
<input type="hidden" name="quest_id" value="{{ quest.id }}">
<input type="submit">
</form>
{% endif %}
<h3>Pages</h3>
<ul>
{% for page in pages %}
<li><a href="{{ url('quest:quest', args=[quest_id, page.page_num]) }}">{{ page.title }}</a></li>
{% endfor %}
</ul>
{% if appendices %}
<h3>Appendices</h3>
<ul>
{% for appendix in appendices %}
<li><a href="{{ url('quest:quest', args=[quest_id, appendix.page_num]) }}">{{ appendix.title }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if vars['next_page'] %}
<div id="nextPageContainer">
<input type="button" id="nextPage" value="Next Page: {{ vars['next_page'].title }}" onclick="window.location.href='{{ url('quest:quest', args=[quest_id, vars['next_page'].page_num]) }}'">
</div>
{% endif %}
</div>
<div id="chatPane" style="display:{% if request.session.get("hide_chat") == True %}none{% else %}flex{% endif %};">
<h1>Chat</h1>
<div id="chatWindow">
{% autoescape false %}
{% for message in chat_messages %}
<div id="msg-{{ message.id }}" class="message">
<div class="messageHeader">
<span class="messageName">{{ message.user.username or quest.anon_name }}</span>
<span class="messageDate">{{ localtime(message.timestamp).strftime('%Y-%m-%d %H:%M:%S') }}</span>
<span class="messageID">No.<a href="javascript:quote('{{ message.id }}')">{{ message.id }}</a></span>
</div>
<div class="messageContent">{{ message.message }}</div>
</div>
<hr>
{% endfor %}
{% endautoescape %}
</div>
<div id="messageTextDiv"><textarea id="messageTextArea" maxlength="512"></textarea></div>
</div>
<div id="preview" style="display:none;"></div>
{% endblock %}

View File

@ -0,0 +1,20 @@
# Generated by Django 2.1.1 on 2018-10-02 14:33
from django.db import migrations
import taggit.managers
class Migration(migrations.Migration):
dependencies = [
('taggit', '0002_auto_20150616_2121'),
('quest', '0003_auto_20180928_0747'),
]
operations = [
migrations.AddField(
model_name='quest',
name='tags',
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
),
]

View File

@ -5,6 +5,7 @@ The main quest models.
from django.db import models
from django.conf import settings
from django.core.validators import MaxValueValidator, MinValueValidator
from taggit.managers import TaggableManager
class Quest(models.Model):
"""
@ -17,6 +18,7 @@ class Quest(models.Model):
anon_name = models.CharField(max_length=20, default="Anonymous")
live = models.BooleanField()
live_time = models.DateTimeField(blank=True, null=True)
tags = TaggableManager()
class Message(models.Model):

View File

@ -177,7 +177,11 @@ function submitWritein(post_id) {
/* Helpers */
function padToTwo(number) {
if (number<=99) { number = ("0"+number).slice(-2); }
if (number >= 0) {
if (number < 10) { number = "0" + number; }
} else {
if (number > -10) { number = "-0" + -1*number; }
}
return number;
}
function strftime(date) {

View File

@ -9,8 +9,10 @@ from . import views
app_name = 'quest'
urlpatterns = [
path('', views.index, name='index'),
path('<int:quest_id>/new_tag', views.new_tag, name='new_tag'),
path('<int:quest_id>/edit_quest', views.edit_quest, name='edit_quest'),
path('<int:quest_id>/<page_num>/edit_quest', views.edit_quest, name='edit_quest'),
path('<int:quest_id>', views.quest, name='quest'),
path('<int:quest_id>/<page_num>', views.quest, name='quest'),
path('new', views.new_quest, name='new_quest'),
]

View File

@ -4,12 +4,14 @@ Quest and quest accessory views.
"""
from datetime import timedelta, datetime, timezone
import bleach
from django.views.decorators.http import require_POST
from django.contrib import messages
from django.http import HttpResponse
from django.shortcuts import render, redirect
from .models import Quest, DiceRoll, PollOption, PollVote, Page
from .forms import EditQuestForm
from .models import Quest, DiceRoll, PollOption, PollVote, Page, Post
from .forms import EditQuestForm, QuestForm, PostForm
def index(request):
"""
@ -18,7 +20,7 @@ def index(request):
return HttpResponse("Hello, world. You're at the quest index.")
def quest(request, quest_id, page_num='1'):
def quest(request, quest_id, page_num='0'):
"""
Arbituary quest page view.
"""
@ -33,7 +35,7 @@ def quest(request, quest_id, page_num='1'):
page = Page.objects.get(quest=quest, page_num=page_num)
except Page.DoesNotExist:
messages.error(request, "Page not found, redirecting you.")
return redirect('quest:quest', quest_id=quest.id, page_num='1')
return redirect('quest:quest', quest_id=quest.id, page_num='0')
posts = quest.post_set.filter(page=page)
# TODO: filter by page_num as well
dice_rolls = DiceRoll.objects.filter(dicecall__post__quest=quest)
@ -41,10 +43,13 @@ def quest(request, quest_id, page_num='1'):
poll_votes = PollVote.objects.filter(option__poll__post__quest=quest)
ip_address = request.META['REMOTE_ADDR']
context = locals()
return render(request, 'quest/quest.html', context)
if page_num == '0':
return render(request, 'quest/quest_homepage.html', context)
else:
return render(request, 'quest/quest.html', context)
def edit_quest(request, quest_id, page_num=1):
def edit_quest(request, quest_id, page_num='0'):
"""
Edit quest page. Only available to the QM.
"""
@ -78,3 +83,61 @@ def edit_quest(request, quest_id, page_num=1):
pass
context = locals()
return render(request, 'quest/edit_quest.html', context)
def new_quest(request):
"""
The page for creating new quests.
"""
if not request.user.is_authenticated:
return redirect('login:index')
if request.method == 'POST':
# TODO: clean the post body
quest = Quest(owner=request.user)
quest_form = QuestForm(request.POST, instance=quest)
post = Post(post_type='text')
post_form = PostForm(request.POST, instance=post)
if all((quest_form.is_valid(), post_form.is_valid())):
quest.live = False
quest.save()
page0 = Page(
quest=quest,
page_num=0,
title="Homepage",
appendix=False,
)
page0.save()
page1 = Page(
quest=quest,
page_num=1,
title="Page 1",
appendix=False,
)
page1.save()
post.quest = quest
post.page = page1
post.save()
return redirect('quest:quest', quest_id=quest.id)
else:
quest_form = QuestForm()
post_form = PostForm()
context = {'quest_form': quest_form, 'post_form': post_form}
return render(request, 'quest/new_quest.html', context)
@require_POST
def new_tag(request, quest_id):
"""Endpoint for adding new tags to a quest."""
if not request.user.is_authenticated:
return redirect('login:index')
tag = request.POST.get('tag', '').strip()
if not tag:
return redirect('quest:quest', quest_id=quest_id, page_num='0')
quest = Quest.objects.get(id=quest_id)
tag = bleach.clean(tag)
if tag in quest.tags.names():
return redirect('quest:quest', quest_id=quest_id, page_num='0')
quest.tags.add(tag)
return redirect('quest:quest', quest_id=quest_id, page_num='0')

5
search/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class SearchConfig(AppConfig):
name = 'search'

View File

@ -0,0 +1,19 @@
{% extends "base.html" %}
{% block title %}Search{% endblock %}
{% block content %}
<form method="get" action="{{ url('search:index') }}">
Author: <input type="text" name="author"><br>
Title: <input type="text" name="title"><br>
Tags: <input type="text" name="tags"><br>
<input type="submit">
</form>
{% if results %}
<table>
{% for quest in results %}
<tr>
<td><a href="{{ url('quest:quest', args=[quest.id, '0']) }}">{{ quest.title }}</a></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python3
"""
create_quest app URL configuration.
Search URL configuration.
"""
from django.urls import path
from . import views
app_name = 'create_quest'
app_name = 'search'
urlpatterns = [
path('', views.index, name='index'),
]

29
search/views.py Normal file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""
/search app views.
"""
from django.shortcuts import render
from quest.models import Quest
from user.models import User
def index(request):
"""The search page index."""
if request.GET:
author = request.GET.get('author')
title = request.GET.get('title')
tags = request.GET.get('tags')
if not any((author, title, tags)):
return
results = Quest.objects.all()
if author:
results = results.filter(
owner__username__unaccent__icontains=author)
if title:
results = results.filter(title__unaccent__icontains=title)
if tags:
results = results.filter(tags__name__in=tags.split())
results = results.distinct()
context = locals()
return render(request, 'search/index.html', context)

View File

@ -3,7 +3,9 @@
/set_session app views.
"""
from django.http import HttpResponse
from django.views.decorators.http import require_POST
@require_POST
def index(request):
"""
A simple API endpoint for setting certain values in the users session.

View File

@ -8,7 +8,7 @@ function toggle_cookie(cookie, state) {
function toggle_header() {
if (document.getElementById('header').style.display == 'flex') {
document.getElementById('header').style.display = 'none';
document.getElementById('headerHidden').style.display = 'initial';
document.getElementById('headerHidden').style.display = 'flex';
toggle_cookie('hide_header', 'on');
}
else {

View File

@ -31,13 +31,15 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.postgres',
'channels',
'taggit',
'user.apps.UserConfig',
'homepage.apps.HomepageConfig',
'create_quest.apps.CreateQuestConfig',
'quest.apps.QuestConfig',
'login.apps.LoginConfig',
'signup.apps.SignupConfig',
'search.apps.SearchConfig',
]
MIDDLEWARE = [
@ -159,3 +161,6 @@ CHANNEL_LAYERS = {
# Image server url
IMG_SVR_URL = "https://img.steelbea.me/"
# Taggit
TAGGIT_CASE_INSENSITIVE = True

View File

@ -9,10 +9,10 @@ urlpatterns = [
path('', include('homepage.urls')),
path('admin/', admin.site.urls),
path('quest/', include('quest.urls')),
path('create_quest/', include('create_quest.urls')),
path('set_session/', include('set_session.urls')),
path('signup/', include('signup.urls')),
path('login/', include('login.urls')),
path('logout/', include('logout.urls')),
path('user/', include('user.urls')),
path('search/', include('search.urls')),
]

4
todo
View File

@ -1,15 +1,13 @@
New Features:
Notifications
Banner images
Search page
Front page to show new quests
Webm posting
(you) counter
Account managament/logout
Account managament
Display profile link in header bar
Tagging system
Quote backlinks
Quest homepage
Improvements:
More options for text posts (lists and so on)