Basic authentication views

This commit is contained in:
Mouse Reeve 2020-01-25 15:25:19 -08:00
parent 4c4011ba75
commit a312791259
13 changed files with 322 additions and 24 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 2.0.13 on 2020-01-25 21:32 # Generated by Django 2.0.13 on 2020-01-25 23:55
from django.conf import settings from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
@ -50,25 +50,36 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('openlibary_key', models.CharField(max_length=255)),
('data', django.contrib.postgres.fields.jsonb.JSONField()),
('added_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)),
],
),
migrations.CreateModel( migrations.CreateModel(
name='Book', name='Book',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False)), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('openlibary_key', models.CharField(max_length=255)), ('openlibary_key', models.CharField(max_length=255)),
('data', django.contrib.postgres.fields.jsonb.JSONField()), ('data', django.contrib.postgres.fields.jsonb.JSONField()),
('added_date', models.DateTimeField(auto_now_add=True)), ('added_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('authors', models.ManyToManyField(to='fedireads.Author')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Review', name='Review',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)), ('name', models.CharField(max_length=255)),
('content', django.contrib.postgres.fields.jsonb.JSONField(max_length=5000)), ('content', django.contrib.postgres.fields.jsonb.JSONField(max_length=5000)),
('created_date', models.DateTimeField(auto_now_add=True)), ('created_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('id', models.AutoField(primary_key=True, serialize=False)),
('star_rating', models.IntegerField(default=0)), ('star_rating', models.IntegerField(default=0)),
('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ('author', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Book')), ('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Book')),
@ -80,26 +91,44 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='Shelf', name='Shelf',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False)), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)), ('name', models.CharField(max_length=100)),
('editable', models.BooleanField(default=True)), ('editable', models.BooleanField(default=True)),
('shelf_type', models.CharField(default='custom', max_length=100)),
('created_date', models.DateTimeField(auto_now_add=True)), ('created_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('books', models.ManyToManyField(to='fedireads.Book')), ],
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), ),
migrations.CreateModel(
name='ShelfBook',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('added_date', models.DateTimeField(auto_now_add=True)),
('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
('book', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Book')),
('shelf', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='fedireads.Shelf')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Work', name='Work',
fields=[ fields=[
('id', models.AutoField(primary_key=True, serialize=False)), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('openlibary_key', models.CharField(max_length=255)), ('openlibary_key', models.CharField(max_length=255)),
('data', django.contrib.postgres.fields.jsonb.JSONField()), ('data', django.contrib.postgres.fields.jsonb.JSONField()),
('added_date', models.DateTimeField(auto_now_add=True)), ('added_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)), ('updated_date', models.DateTimeField(auto_now=True)),
('added_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
], ],
), ),
migrations.AddField(
model_name='shelf',
name='books',
field=models.ManyToManyField(through='fedireads.ShelfBook', to='fedireads.Book'),
),
migrations.AddField(
model_name='shelf',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL),
),
migrations.AddField( migrations.AddField(
model_name='book', model_name='book',
name='works', name='works',

View file

@ -1,5 +1,6 @@
''' database schema for the whole dang thing ''' ''' database schema for the whole dang thing '''
from django.db import models from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import JSONField from django.contrib.postgres.fields import JSONField
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
@ -28,10 +29,27 @@ class User(AbstractUser):
super().save(*args, **kwargs) super().save(*args, **kwargs)
@receiver(models.signals.post_save, sender=User)
def execute_after_save(sender, instance, created, *args, **kwargs):
if not created:
return
shelves = [{
'name': 'To Read',
'type': 'to-read',
}, {
'name': 'Currently Reading',
'type': 'reading',
}, {
'name': 'Read',
'type': 'read',
}]
for shelf in shelves:
Shelf(name=shelf['name'], shelf_type=shelf['type'], user=instance, editable=False).save()
class Message(models.Model): class Message(models.Model):
''' any kind of user post, incl. reviews, replies, and status updates ''' ''' any kind of user post, incl. reviews, replies, and status updates '''
id = models.AutoField(primary_key=True)
author = models.ForeignKey('User', on_delete=models.PROTECT) author = models.ForeignKey('User', on_delete=models.PROTECT)
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
content = JSONField(max_length=5000) content = JSONField(max_length=5000)
@ -43,35 +61,52 @@ class Message(models.Model):
class Review(Message): class Review(Message):
id = models.AutoField(primary_key=True)
book = models.ForeignKey('Book', on_delete=models.PROTECT) book = models.ForeignKey('Book', on_delete=models.PROTECT)
star_rating = models.IntegerField(default=0) star_rating = models.IntegerField(default=0)
class Shelf(models.Model): class Shelf(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100) name = models.CharField(max_length=100)
user = models.ForeignKey('User', on_delete=models.PROTECT) user = models.ForeignKey('User', on_delete=models.PROTECT)
editable = models.BooleanField(default=True) editable = models.BooleanField(default=True)
books = models.ManyToManyField('Book', symmetrical=False) shelf_type = models.CharField(default='custom', max_length=100)
books = models.ManyToManyField(
'Book',
symmetrical=False,
through='ShelfBook',
through_fields=('shelf', 'book')
)
created_date = models.DateTimeField(auto_now_add=True) created_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
class ShelfBook(models.Model):
# many to many join table for books and shelves
book = models.ForeignKey('Book', on_delete=models.PROTECT)
shelf = models.ForeignKey('Shelf', on_delete=models.PROTECT)
added_by = models.ForeignKey('User', blank=True, null=True, on_delete=models.PROTECT)
added_date = models.DateTimeField(auto_now_add=True)
class Book(models.Model): class Book(models.Model):
''' a non-canonical copy from open library ''' ''' a non-canonical copy from open library '''
id = models.AutoField(primary_key=True)
openlibary_key = models.CharField(max_length=255) openlibary_key = models.CharField(max_length=255)
data = JSONField() data = JSONField()
works = models.ManyToManyField('Work') works = models.ManyToManyField('Work')
authors = models.ManyToManyField('Author')
added_by = models.ForeignKey('User', on_delete=models.PROTECT, blank=True, null=True) added_by = models.ForeignKey('User', on_delete=models.PROTECT, blank=True, null=True)
added_date = models.DateTimeField(auto_now_add=True) added_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
class Work(models.Model): class Work(models.Model):
id = models.AutoField(primary_key=True)
openlibary_key = models.CharField(max_length=255) openlibary_key = models.CharField(max_length=255)
data = JSONField() data = JSONField()
added_by = models.ForeignKey('User', on_delete=models.PROTECT, blank=True, null=True)
added_date = models.DateTimeField(auto_now_add=True) added_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True) updated_date = models.DateTimeField(auto_now=True)
class Author(models.Model):
openlibary_key = models.CharField(max_length=255)
data = JSONField()
added_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)

View file

@ -2,7 +2,7 @@
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.core import serializers from django.core import serializers
from fedireads.models import Book, Work from fedireads.models import Author, Book, Work
import requests import requests
openlibrary_url = 'https://openlibrary.org' openlibrary_url = 'https://openlibrary.org'
@ -22,15 +22,28 @@ def get_book(request, olkey):
for work_id in data['works']: for work_id in data['works']:
work_id = work_id['key'] work_id = work_id['key']
book.works.add(get_or_create_work(work_id)) book.works.add(get_or_create_work(work_id))
for author_id in data['authors']:
author_id = author_id['key']
book.authors.add(get_or_create_author(author_id))
return HttpResponse(serializers.serialize('json', [book])) return HttpResponse(serializers.serialize('json', [book]))
def get_or_create_work(olkey): def get_or_create_work(olkey):
try: try:
work = Work.objects.get(openlibary_key=olkey) work = Work.objects.get(openlibary_key=olkey)
except ObjectDoesNotExist: except ObjectDoesNotExist:
response = requests.get(openlibrary_url + '/work/' + olkey +'.json') response = requests.get(openlibrary_url + olkey + '.json')
data = response.json() data = response.json()
work = Work(openlibary_key=olkey, data=data) work = Work(openlibary_key=olkey, data=data)
work.save() work.save()
return work return work
def get_or_create_author(olkey):
try:
author = Author.objects.get(openlibary_key=olkey)
except ObjectDoesNotExist:
response = requests.get(openlibrary_url + olkey + '.json')
data = response.json()
author = Author(openlibary_key=olkey, data=data)
author.save()
return author

View file

@ -55,7 +55,7 @@ ROOT_URLCONF = 'fedireads.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [], 'DIRS': ['templates'],
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [
@ -68,6 +68,7 @@ TEMPLATES = [
}, },
] ]
WSGI_APPLICATION = 'fedireads.wsgi.application' WSGI_APPLICATION = 'fedireads.wsgi.application'
@ -85,6 +86,7 @@ DATABASES = {
} }
} }
LOGIN_URL = 'login/'
AUTH_USER_MODEL = 'fedireads.User' AUTH_USER_MODEL = 'fedireads.User'
# Password validation # Password validation

View file

@ -0,0 +1,69 @@
* {
margin: 0;
padding: 0;
line-height: 1.3em;
overflow: auto;
}
body > * > * {
margin: 0 auto;
padding: 1rem;
max-width: 75rem;
min-width: 30rem;
}
#top-bar {
height: 4rem;
border-bottom: 1px solid #aaa;
box-shadow: 0 0.5em 0.5em -0.6em #666;
margin-bottom: 1em;
overflow: auto;
}
#branding {
font-size: 2em;
}
header > div:first-child {
float: left;
}
header > div:last-child {
float: right;
}
#sidebar {
width: 30%;
float: left;
}
.user-pic {
width: 2em;
height: auto;
border-radius: 50%;
vertical-align: middle;
}
.book-preview {
overflow: auto;
margin-bottom: 1em;
}
.book-preview img {
float: left;
margin-right: 0.5em;
}
.update {
border: 1px solid #333;
border-radius: 0.2rem;
margin-bottom: 1em;
}
.update > * {
padding: 1em;
}
.interact {
background-color: #eee;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,51 @@
{% extends 'layout.html' %}
{% block content %}
<div id="sidebar">
{% for shelf in shelves %}
<h1>{{ shelf.name }}</h1>
{% for book in shelf.books.all %}
<div class="book-preview">
<img class="cover" src="static/images/small.jpg">
<p class="title">{{ book.data.title }}</p>
<p>by <a href="" class="author">{{ book.authors.first.data.name }}</a></p>
{% if shelf.type == 'reading' %}
<button>Done reading</button>
{% endif %}
</div>
{% endfor %}
{% endfor %}
</div>
<div id="main">
<div class="update">
<div class="user-preview">
<img class="user-pic" src="static/images/profile.jpg">
<span><a href="" class="user">Mouse</a> is currently reading</span>
</div>
<div class="book-preview">
<img class="cover" src="static/images/med.jpg">
<p class="title">Moby Dick</p>
<p>by <a href="" class="author">Herman Melville</a></p>
<p>"Command the murderous chalices! Drink ye harpooners! Drink and swear, ye men that man the deathful whaleboat's bow -- Death to Moby Dick!" So Captain Ahab binds his crew to fulfil his obsession -- the destruction of the great white whale. Under his lordly but maniacal command the Pequod's commercial mission is perverted to one of vengeance...</p>
</div>
<div class="interact">
<span>⭐️ Like</span>
<span>💬 <input type="text"></input></span>
</div>
</div>
<div class="update">
<img class="user-pic" src="static/images/profile.jpg">
<span><a href="" class="user">Mouse</a> is currently reading</span>
<div class="book-preview">
<img class="cover" src="static/images/med.jpg">
<p class="title">Moby Dick</p>
<p>by <a href="" class="author">Herman Melville</a></p>
<p>"Command the murderous chalices! Drink ye harpooners! Drink and swear, ye men that man the deathful whaleboat's bow -- Death to Moby Dick!" So Captain Ahab binds his crew to fulfil his obsession -- the destruction of the great white whale. Under his lordly but maniacal command the Pequod's commercial mission is perverted to one of vengeance...</p>
</div>
<div class="interact">
<span>⭐️ Like</span>
<span>💬 <input type="text"></input></span>
</div>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>FediReads</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link type="text/css" rel="stylesheet" href="/static/format.css">
<link rel="shortcut icon" type="image/x-icon" href="/static/images/favicon.ico">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="FediReads">
<meta name="og:title" content="FediReads">
<meta name="twitter:description" content="Federated Social Reading">
<meta name="og:description" content="Federated Social Reading">
<meta name="twitter:creator" content="@tripofmice">
<meta name="twitter:site" content="@tripofmice">
</head>
<body>
<div id="top-bar">
<header>
<div id="branding">📚FediReads</div>
<div>
<div id="account">
{% if user.is_authenticated %}
<form name="logout" action="/logout/" method="post">
Welcome, {{ user.username }}
<input type="submit" value="Log out"></input>
</form>
{% else %}
<form name="login" action="/login/" method="post">
<input type="text" name="username"></input>
<input type="password" name="password"></input>
<input type="submit" value="Log in"></input>
</form>
{% endif %}
</div>
<div id="search">
<form action="search">
<input type="text" name="q"></input>
<input type="submit" value="🔍"></input>
</form>
</div>
</div>
</header>
</div>
<div id="content">
<div>
{% block content %}
{% endblock %}
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,8 @@
{% extends 'layout.html' %}
{% block content %}
<form name="login" method="post">
<input type="text" name="username"></input>
<input type="password" name="password"></input>
<input type="submit"></input>
</form>
{% endblock %}

View file

@ -19,6 +19,9 @@ from fedireads import activitystream, openlibrary, views
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('', views.home),
path('login/', views.user_login),
path('logout/', views.user_logout),
path('api/book/<str:olkey>', openlibrary.get_book), path('api/book/<str:olkey>', openlibrary.get_book),
path('webfinger/', activitystream.webfinger), path('webfinger/', activitystream.webfinger),
] ]

View file

@ -1,11 +1,39 @@
''' application views/pages '''
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth import authenticate, login, logout
from django.shortcuts import redirect
from django.template.response import TemplateResponse
from django.views.decorators.csrf import csrf_exempt
from fedireads.models import Shelf
@login_required @login_required
def account_page(request): def home(request):
return 'hi' ''' user feed '''
shelves = Shelf.objects.filter(user=request.user.id)
data = {
'user': request.user,
'shelves': shelves,
}
return TemplateResponse(request, 'feed.html', data)
def webfinger(request): @csrf_exempt
return 'hello' def user_login(request):
''' authentication '''
# send user to the login page
if request.method == 'GET':
return TemplateResponse(request, 'login.html')
def api(request): # authenticate user
return 'hey' username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect(request.GET.get('next', '/'))
return TemplateResponse(request, 'login.html')
@csrf_exempt
@login_required
def user_logout(request):
logout(request)
return redirect('/')