forked from mirrors/bookwyrm
Basic authentication views
This commit is contained in:
parent
4c4011ba75
commit
a312791259
13 changed files with 322 additions and 24 deletions
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
69
fedireads/static/format.css
Normal file
69
fedireads/static/format.css
Normal 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;
|
||||
}
|
BIN
fedireads/static/images/med.jpg
Normal file
BIN
fedireads/static/images/med.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
fedireads/static/images/profile.jpg
Normal file
BIN
fedireads/static/images/profile.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
BIN
fedireads/static/images/small.jpg
Normal file
BIN
fedireads/static/images/small.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
51
fedireads/templates/feed.html
Normal file
51
fedireads/templates/feed.html
Normal 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 %}
|
60
fedireads/templates/layout.html
Normal file
60
fedireads/templates/layout.html
Normal 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>
|
||||
|
8
fedireads/templates/login.html
Normal file
8
fedireads/templates/login.html
Normal 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 %}
|
|
@ -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),
|
||||
]
|
||||
|
|
|
@ -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('/')
|
||||
|
|
Loading…
Reference in a new issue