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
import django.contrib.auth.models
@ -50,25 +50,36 @@ class Migration(migrations.Migration):
('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(
name='Book',
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)),
('data', django.contrib.postgres.fields.jsonb.JSONField()),
('added_date', models.DateTimeField(auto_now_add=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)),
('authors', models.ManyToManyField(to='fedireads.Author')),
],
),
migrations.CreateModel(
name='Review',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('content', django.contrib.postgres.fields.jsonb.JSONField(max_length=5000)),
('created_date', models.DateTimeField(auto_now_add=True)),
('updated_date', models.DateTimeField(auto_now=True)),
('id', models.AutoField(primary_key=True, serialize=False)),
('star_rating', models.IntegerField(default=0)),
('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')),
@ -80,26 +91,44 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Shelf',
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)),
('editable', models.BooleanField(default=True)),
('shelf_type', models.CharField(default='custom', max_length=100)),
('created_date', models.DateTimeField(auto_now_add=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(
name='Work',
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)),
('data', django.contrib.postgres.fields.jsonb.JSONField()),
('added_date', models.DateTimeField(auto_now_add=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(
model_name='book',
name='works',

View file

@ -1,5 +1,6 @@
''' database schema for the whole dang thing '''
from django.db import models
from django.dispatch import receiver
from django.contrib.auth.models import AbstractUser
from django.contrib.postgres.fields import JSONField
from Crypto.PublicKey import RSA
@ -28,10 +29,27 @@ class User(AbstractUser):
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):
''' 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)
name = models.CharField(max_length=255)
content = JSONField(max_length=5000)
@ -43,35 +61,52 @@ class Message(models.Model):
class Review(Message):
id = models.AutoField(primary_key=True)
book = models.ForeignKey('Book', on_delete=models.PROTECT)
star_rating = models.IntegerField(default=0)
class Shelf(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
user = models.ForeignKey('User', on_delete=models.PROTECT)
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)
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):
''' a non-canonical copy from open library '''
id = models.AutoField(primary_key=True)
openlibary_key = models.CharField(max_length=255)
data = JSONField()
works = models.ManyToManyField('Work')
authors = models.ManyToManyField('Author')
added_by = models.ForeignKey('User', on_delete=models.PROTECT, blank=True, null=True)
added_date = models.DateTimeField(auto_now_add=True)
updated_date = models.DateTimeField(auto_now=True)
class Work(models.Model):
id = models.AutoField(primary_key=True)
openlibary_key = models.CharField(max_length=255)
data = JSONField()
added_by = models.ForeignKey('User', on_delete=models.PROTECT, blank=True, null=True)
added_date = models.DateTimeField(auto_now_add=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.core.exceptions import ObjectDoesNotExist
from django.core import serializers
from fedireads.models import Book, Work
from fedireads.models import Author, Book, Work
import requests
openlibrary_url = 'https://openlibrary.org'
@ -22,15 +22,28 @@ def get_book(request, olkey):
for work_id in data['works']:
work_id = work_id['key']
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]))
def get_or_create_work(olkey):
try:
work = Work.objects.get(openlibary_key=olkey)
except ObjectDoesNotExist:
response = requests.get(openlibrary_url + '/work/' + olkey +'.json')
response = requests.get(openlibrary_url + olkey + '.json')
data = response.json()
work = Work(openlibary_key=olkey, data=data)
work.save()
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 = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -68,6 +68,7 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'fedireads.wsgi.application'
@ -85,6 +86,7 @@ DATABASES = {
}
}
LOGIN_URL = 'login/'
AUTH_USER_MODEL = 'fedireads.User'
# 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 = [
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('webfinger/', activitystream.webfinger),
]

View file

@ -1,11 +1,39 @@
''' application views/pages '''
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
def account_page(request):
return 'hi'
def home(request):
''' 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):
return 'hello'
@csrf_exempt
def user_login(request):
''' authentication '''
# send user to the login page
if request.method == 'GET':
return TemplateResponse(request, 'login.html')
def api(request):
return 'hey'
# authenticate user
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('/')