diff --git a/fedireads/migrations/0001_initial.py b/fedireads/migrations/0001_initial.py
index f647d886..fcc00f97 100644
--- a/fedireads/migrations/0001_initial.py
+++ b/fedireads/migrations/0001_initial.py
@@ -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',
diff --git a/fedireads/models.py b/fedireads/models.py
index 76d9630e..b32a0621 100644
--- a/fedireads/models.py
+++ b/fedireads/models.py
@@ -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)
+
diff --git a/fedireads/openlibrary.py b/fedireads/openlibrary.py
index 3953da2d..66fc20bb 100644
--- a/fedireads/openlibrary.py
+++ b/fedireads/openlibrary.py
@@ -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
+
diff --git a/fedireads/settings.py b/fedireads/settings.py
index 4ba4a2ba..c08cb7c2 100644
--- a/fedireads/settings.py
+++ b/fedireads/settings.py
@@ -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
diff --git a/fedireads/static/format.css b/fedireads/static/format.css
new file mode 100644
index 00000000..7e0ea6c5
--- /dev/null
+++ b/fedireads/static/format.css
@@ -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;
+}
diff --git a/fedireads/static/images/med.jpg b/fedireads/static/images/med.jpg
new file mode 100644
index 00000000..c275cd1c
Binary files /dev/null and b/fedireads/static/images/med.jpg differ
diff --git a/fedireads/static/images/profile.jpg b/fedireads/static/images/profile.jpg
new file mode 100644
index 00000000..f150ceab
Binary files /dev/null and b/fedireads/static/images/profile.jpg differ
diff --git a/fedireads/static/images/small.jpg b/fedireads/static/images/small.jpg
new file mode 100644
index 00000000..158163b6
Binary files /dev/null and b/fedireads/static/images/small.jpg differ
diff --git a/fedireads/templates/feed.html b/fedireads/templates/feed.html
new file mode 100644
index 00000000..1d0c7f45
--- /dev/null
+++ b/fedireads/templates/feed.html
@@ -0,0 +1,51 @@
+{% extends 'layout.html' %}
+{% block content %}
+
+
+
+
+
+
+
Mouse is currently reading
+
+
+
+
Moby Dick
+
by Herman Melville
+
"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...
+
+
+ ⭐️ Like
+ 💬
+
+
+
+
+
Mouse is currently reading
+
+
+
Moby Dick
+
by Herman Melville
+
"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...
+
+
+ ⭐️ Like
+ 💬
+
+
+
+{% endblock %}
diff --git a/fedireads/templates/layout.html b/fedireads/templates/layout.html
new file mode 100644
index 00000000..c1329102
--- /dev/null
+++ b/fedireads/templates/layout.html
@@ -0,0 +1,60 @@
+
+
+
+ FediReads
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
diff --git a/fedireads/templates/login.html b/fedireads/templates/login.html
new file mode 100644
index 00000000..fe6a319d
--- /dev/null
+++ b/fedireads/templates/login.html
@@ -0,0 +1,8 @@
+{% extends 'layout.html' %}
+{% block content %}
+
+{% endblock %}
diff --git a/fedireads/urls.py b/fedireads/urls.py
index e4a94ebd..9c964482 100644
--- a/fedireads/urls.py
+++ b/fedireads/urls.py
@@ -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/', openlibrary.get_book),
path('webfinger/', activitystream.webfinger),
]
diff --git a/fedireads/views.py b/fedireads/views.py
index 1fbaa807..7b2e360c 100644
--- a/fedireads/views.py
+++ b/fedireads/views.py
@@ -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('/')