forked from mirrors/bookwyrm
Redesign (front page, login page)
This commit is contained in:
parent
67e7eaaf85
commit
3efc8d45c3
30 changed files with 769 additions and 249 deletions
|
@ -28,15 +28,15 @@ class RegisterForm(ModelForm):
|
|||
class ReviewForm(ModelForm):
|
||||
class Meta:
|
||||
model = models.Review
|
||||
fields = ['name', 'content', 'rating']
|
||||
fields = ['name', 'rating', 'content']
|
||||
help_texts = {f: None for f in fields}
|
||||
review_content = IntegerField(validators=[
|
||||
content = IntegerField(validators=[
|
||||
MinValueValidator(0), MaxValueValidator(5)
|
||||
])
|
||||
labels = {
|
||||
'name': 'Title',
|
||||
'review_content': 'Review',
|
||||
'rating': 'Rating (out of 5)',
|
||||
'content': 'Review',
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -46,7 +46,6 @@ class Status(FedireadsModel):
|
|||
return '%s/%s/%d' % (base_path, model_name, self.id)
|
||||
|
||||
|
||||
|
||||
class Review(Status):
|
||||
''' a book review '''
|
||||
name = models.CharField(max_length=255)
|
||||
|
|
|
@ -111,12 +111,14 @@ def handle_outgoing_accept(user, to_follow, follow_request):
|
|||
broadcast(to_follow, activity, recipient)
|
||||
|
||||
def handle_outgoing_reject(user, to_follow, relationship):
|
||||
''' a local user who managed follows rejects a follow request '''
|
||||
relationship.delete()
|
||||
|
||||
activity = activitypub.get_reject(to_follow, relationship)
|
||||
recipient = get_recipients(to_follow, 'direct', direct_recipients=[user])
|
||||
broadcast(to_follow, activity, recipient)
|
||||
|
||||
|
||||
def handle_shelve(user, book, shelf):
|
||||
''' a local user is getting a book put on their shelf '''
|
||||
# update the database
|
||||
|
@ -132,9 +134,10 @@ def handle_shelve(user, book, shelf):
|
|||
'reading': 'started reading',
|
||||
'read': 'finished reading'
|
||||
}[shelf.identifier]
|
||||
name = user.name if user.name else user.localname
|
||||
message = '%s %s %s' % (name, verb, book.title)
|
||||
message = '%s %s' % (verb, book.title)
|
||||
status = create_status(user, message, mention_books=[book])
|
||||
status.status_type = 'Update'
|
||||
status.save()
|
||||
|
||||
activity = activitypub.get_status(status)
|
||||
create_activity = activitypub.get_create(user, activity)
|
||||
|
|
BIN
fedireads/static/fonts/icomoon.eot
Normal file
BIN
fedireads/static/fonts/icomoon.eot
Normal file
Binary file not shown.
36
fedireads/static/fonts/icomoon.svg
Normal file
36
fedireads/static/fonts/icomoon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 22 KiB |
BIN
fedireads/static/fonts/icomoon.ttf
Normal file
BIN
fedireads/static/fonts/icomoon.ttf
Normal file
Binary file not shown.
BIN
fedireads/static/fonts/icomoon.woff
Normal file
BIN
fedireads/static/fonts/icomoon.woff
Normal file
Binary file not shown.
|
@ -10,13 +10,21 @@ html {
|
|||
background-color: #FFF;
|
||||
color: black;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00F;
|
||||
body {
|
||||
padding-top: 90px;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #808;
|
||||
|
||||
a {
|
||||
color: #247BA0;
|
||||
}
|
||||
|
||||
input, button {
|
||||
padding: 0.2em 0.5em;
|
||||
}
|
||||
button {
|
||||
cursor: pointer;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
|
@ -29,16 +37,21 @@ h1 {
|
|||
|
||||
h2 {
|
||||
font-size: 1rem;
|
||||
background-color: #B2DBBF;
|
||||
padding: 0.5rem 0.2rem;
|
||||
margin-bottom: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
#top-bar {
|
||||
background-color: #70C1B3;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
padding: 0.5rem;
|
||||
border-bottom: 3px solid #247BA0;
|
||||
margin-bottom: 1em;
|
||||
width: 100%;
|
||||
background-color: #FFF;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
height: 47px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#warning {
|
||||
|
@ -46,62 +59,183 @@ h2 {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
#branding, #actions {
|
||||
margin: 0 1rem;
|
||||
}
|
||||
#branding {
|
||||
flex-grow: 1;
|
||||
font-size: 2rem;
|
||||
}
|
||||
#branding a {
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
#actions {
|
||||
flex-grow: 0;
|
||||
text-align: right;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
#main, header > div {
|
||||
#actions > * {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#notifications .icon {
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
#notifications a {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
position: relative;
|
||||
top: 0.2rem;
|
||||
}
|
||||
#notifications .count {
|
||||
background-color: #FF1654;
|
||||
color: white;
|
||||
font-size: 0.85rem;
|
||||
border-radius: 50%;
|
||||
display: block;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: -0.65rem;
|
||||
right: -0.5rem;
|
||||
height: 1rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
button .icon {
|
||||
font-size: 1.1rem;
|
||||
vertical-align: sub;
|
||||
}
|
||||
#search button {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#main, header {
|
||||
margin: 0 auto;
|
||||
max-width: 55rem;
|
||||
padding-right: 1em;
|
||||
}
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
max-width: 75rem;
|
||||
width: 100%;
|
||||
}
|
||||
@media (max-width: 600px) {
|
||||
#main {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
|
||||
#feed, #content, #sidebar {
|
||||
ul.menu {
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
margin-top: 1.5rem;
|
||||
flex-grow: 1;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
ul.menu li {
|
||||
display: inline-block;
|
||||
background-color: white;
|
||||
padding: 0 0.5em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
ul.menu a {
|
||||
color: #555;
|
||||
text-decoration: none;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.pulldown-container {
|
||||
position: relative;
|
||||
}
|
||||
.pulldown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
list-style: none;
|
||||
background: white;
|
||||
padding: 1em;
|
||||
text-align: right;
|
||||
right: 0;
|
||||
box-shadow: 0 5px 10px rgba(0,0,0,0.15);
|
||||
width: max-content;
|
||||
}
|
||||
.pulldown-container:hover .pulldown {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0 1rem 1rem 0;
|
||||
padding-top: 70px;
|
||||
position: relative;
|
||||
top: -50px;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
min-width: 20rem;
|
||||
margin-right: 0;
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.row > * {
|
||||
flex-grow: 1;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.login form {
|
||||
margin-top: 1em;
|
||||
}
|
||||
.login form p {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
.login form label {
|
||||
width: 0;
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
form input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.content-container button {
|
||||
border: none;
|
||||
background-color: #247BA0;
|
||||
color: white;
|
||||
padding: 0.3em 0.8em;
|
||||
font-size: 0.9em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
button.secondary {
|
||||
background-color: #EEE;
|
||||
border: 2px solid #247BA0;
|
||||
color: #247BA0;
|
||||
}
|
||||
|
||||
.login h2 {
|
||||
border-bottom: 3px solid #B2DBBF;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 3px solid #FF1654;
|
||||
padding-left: 1em;
|
||||
}
|
||||
.tab.active {
|
||||
.tabs.secondary {
|
||||
border-bottom: 3px solid #247BA0;
|
||||
}
|
||||
.tab {
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 0.25em 0.25em 0 0;
|
||||
}
|
||||
.secondary .tab {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
.tabs .tab.active {
|
||||
background-color: #FF1654;
|
||||
}
|
||||
.tabs.secondary .tab.active {
|
||||
background-color: #247BA0;
|
||||
}
|
||||
.tab.active a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.user-pic {
|
||||
width: 2rem;
|
||||
height: auto;
|
||||
height: 2rem;
|
||||
border-radius: 50%;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
bottom: 0.5em;
|
||||
bottom: 0.35em;
|
||||
}
|
||||
|
||||
.review-form label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.time-ago {
|
||||
|
@ -110,7 +244,7 @@ h2 {
|
|||
}
|
||||
|
||||
.book-preview {
|
||||
overflow: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.book-preview img {
|
||||
|
@ -122,17 +256,89 @@ h2 {
|
|||
float: left;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
margin: 1rem;
|
||||
}
|
||||
.content-container > * {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.all-shelves {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-y: hidden;
|
||||
margin-left: 0;
|
||||
}
|
||||
.all-shelves > div {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.all-shelves > div:last-child {
|
||||
padding-right: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.all-shelves > div > * {
|
||||
padding: 0;
|
||||
}
|
||||
.all-shelves > div:first-child > * {
|
||||
padding-left: 1em;
|
||||
}
|
||||
.all-shelves h2 {
|
||||
border-bottom: 3px solid #B2DBBF;
|
||||
}
|
||||
|
||||
.covers-shelf {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
.covers-shelf .book-preview {
|
||||
margin-right: 1em;
|
||||
font-size: 0.9em;
|
||||
overflow: unset;
|
||||
width: min-content;
|
||||
position: relative;
|
||||
}
|
||||
.covers-shelf .book-preview button {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
border: none;
|
||||
}
|
||||
.covers-shelf .book-preview:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
.covers-shelf .book-preview:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.covers-shelf .book-preview:hover img {
|
||||
box-shadow: #F3FFBD 0em 0em 1em 1em;
|
||||
}
|
||||
.covers-shelf .book-preview img {
|
||||
float: none;
|
||||
height: 11rem;
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.close {
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.compose-suggestion {
|
||||
display: none;
|
||||
}
|
||||
.compose-suggestion.visible {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.book-cover.small {
|
||||
width: 50px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#content > div, #feed > div, #sidebar > div {
|
||||
background-color: #EFEFEF;
|
||||
margin: 1rem 0 0 1rem;
|
||||
}
|
||||
#content > div > *, #feed > div > *, #sidebar > div > * {
|
||||
padding: 1rem;
|
||||
.compose-suggestion .book-preview {
|
||||
background-color: #EEE;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.tag {
|
||||
|
@ -150,22 +356,14 @@ h2 {
|
|||
width: 30rem;
|
||||
height: 10rem;
|
||||
}
|
||||
.review {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.interaction {
|
||||
background-color: #F3FFBD;
|
||||
clear: both;
|
||||
margin-top: 1rem;
|
||||
background-color: #B2DBBF;
|
||||
border-radius: 0 0 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.interaction textarea {
|
||||
|
@ -201,27 +399,44 @@ th, td {
|
|||
.comment-thread .reply h2 {
|
||||
background: none;
|
||||
}
|
||||
.post.main {
|
||||
background-color: #F3FFBD;
|
||||
}
|
||||
.post {
|
||||
background-color: #EFEFEF;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
.post h2, .compose-suggestion h2 {
|
||||
position: relative;
|
||||
right: 2em;
|
||||
}
|
||||
.post .user-pic, .compose-suggestion .user-pic {
|
||||
right: 0.25em;
|
||||
}
|
||||
|
||||
.comment-thread .post {
|
||||
margin-left: 4em;
|
||||
border-left: 2px solid #247BA0;
|
||||
}
|
||||
.post.depth-1 {
|
||||
.comment-thread .post.depth-1 {
|
||||
margin-left: 0;
|
||||
border: none;
|
||||
}
|
||||
.post.depth-2 {
|
||||
.comment-thread .post.depth-2 {
|
||||
margin-left: 1em;
|
||||
}
|
||||
.post.depth-3 {
|
||||
.comment-thread .post.depth-3 {
|
||||
margin-left: 2em;
|
||||
}
|
||||
.post.depth-4 {
|
||||
.comment-thread .post.depth-4 {
|
||||
margin-left: 3em;
|
||||
}
|
||||
|
||||
.unread {
|
||||
background-color: #F3FFBD;
|
||||
background-color: #DDD;
|
||||
}
|
||||
|
||||
.hidden-text {
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
138
fedireads/static/icons.css
Normal file
138
fedireads/static/icons.css
Normal file
|
@ -0,0 +1,138 @@
|
|||
@font-face {
|
||||
font-family: 'icomoon';
|
||||
src: url('fonts/icomoon.eot?v0wquk');
|
||||
src: url('fonts/icomoon.eot?v0wquk#iefix') format('embedded-opentype'),
|
||||
url('fonts/icomoon.ttf?v0wquk') format('truetype'),
|
||||
url('fonts/icomoon.woff?v0wquk') format('woff'),
|
||||
url('fonts/icomoon.svg?v0wquk#icomoon') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
[class^="icon-"], [class*=" icon-"] {
|
||||
/* use !important to prevent issues with browser extensions that change fonts */
|
||||
font-family: 'icomoon' !important;
|
||||
speak: none;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-variant: normal;
|
||||
text-transform: none;
|
||||
line-height: 1;
|
||||
|
||||
/* Better Font Rendering =========== */
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-arrow-right:before {
|
||||
content: "\e900";
|
||||
}
|
||||
.icon-arrow-left:before {
|
||||
content: "\e910";
|
||||
}
|
||||
.icon-arrow-up:before {
|
||||
content: "\e911";
|
||||
}
|
||||
.icon-arrow-down:before {
|
||||
content: "\e912";
|
||||
}
|
||||
.icon-x:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-cancel:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-close:before {
|
||||
content: "\e902";
|
||||
}
|
||||
.icon-search:before {
|
||||
content: "\e986";
|
||||
}
|
||||
.icon-star-empty:before {
|
||||
content: "\e9d7";
|
||||
}
|
||||
.icon-star-half:before {
|
||||
content: "\e9d8";
|
||||
}
|
||||
.icon-star-full:before {
|
||||
content: "\e9d9";
|
||||
}
|
||||
.icon-heart:before {
|
||||
content: "\e9da";
|
||||
}
|
||||
.icon-local:before {
|
||||
content: "\e914";
|
||||
}
|
||||
.icon-home:before {
|
||||
content: "\e913";
|
||||
}
|
||||
.icon-quote-close:before {
|
||||
content: "\e903";
|
||||
}
|
||||
.icon-quote-open:before {
|
||||
content: "\e904";
|
||||
}
|
||||
.icon-image:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-photo:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-picture-o:before {
|
||||
content: "\e905";
|
||||
}
|
||||
.icon-pencil:before {
|
||||
content: "\e906";
|
||||
}
|
||||
.icon-list:before {
|
||||
content: "\e907";
|
||||
}
|
||||
.icon-unlock:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-unlisted:before {
|
||||
content: "\e908";
|
||||
}
|
||||
.icon-globe:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-global:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-federated:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-public:before {
|
||||
content: "\e909";
|
||||
}
|
||||
.icon-lock:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
.icon-private:before {
|
||||
content: "\e90a";
|
||||
}
|
||||
.icon-chain-broken:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-unlink:before {
|
||||
content: "\e90b";
|
||||
}
|
||||
.icon-chain:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-link:before {
|
||||
content: "\e90c";
|
||||
}
|
||||
.icon-comments:before {
|
||||
content: "\e90d";
|
||||
}
|
||||
.icon-comment:before {
|
||||
content: "\e90e";
|
||||
}
|
||||
.icon-boost:before {
|
||||
content: "\e90f";
|
||||
}
|
||||
.icon-bell:before {
|
||||
content: "\e901";
|
||||
}
|
BIN
fedireads/static/images/logo-small.png
Normal file
BIN
fedireads/static/images/logo-small.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.9 KiB |
BIN
fedireads/static/images/logo.png
Normal file
BIN
fedireads/static/images/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
11
fedireads/static/js/feed.js
Normal file
11
fedireads/static/js/feed.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
function show_compose(element) {
|
||||
var visible_compose_boxes = document.getElementsByClassName('visible');
|
||||
for (var i = 0; i < visible_compose_boxes.length; i++) {
|
||||
visible_compose_boxes[i].className = 'compose-suggestion';
|
||||
}
|
||||
|
||||
var target_id = 'compose-' + element.id;
|
||||
var target = document.getElementById(target_id);
|
||||
target.className += ' visible';
|
||||
}
|
||||
|
8
fedireads/static/js/shared.js
Normal file
8
fedireads/static/js/shared.js
Normal file
|
@ -0,0 +1,8 @@
|
|||
function hide_element(element) {
|
||||
var classes = element.parentElement.className;
|
||||
element.parentElement.className = classes.replace('visible', '');
|
||||
}
|
||||
|
||||
function favorite(element) {
|
||||
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
<div>
|
||||
<h2><q>{{ book.title }}</q> and You</h2>
|
||||
<p>{% if shelf %}On shelf <q>{{ shelf.name }}</q>{% endif %}</p>
|
||||
{% include 'snippets/shelve-button.html' with book=book pulldown=True %}
|
||||
{% include 'snippets/shelve_button.html' with book=book pulldown=True %}
|
||||
|
||||
<div id="tag-cloud">
|
||||
{% for tag in user_tags %}
|
||||
|
|
|
@ -2,44 +2,52 @@
|
|||
{% load fr_display %}
|
||||
{% block content %}
|
||||
|
||||
<div id="sidebar">
|
||||
<div class="all-shelves content-container">
|
||||
{% for shelf in shelves %}
|
||||
{% if shelf.books %}
|
||||
<div>
|
||||
{% if shelves %}
|
||||
{% for shelf in shelves %}
|
||||
<h2>{{ shelf.name }}</h2>
|
||||
{% for book in shelf.books %}
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book.html' with book=book size="small" %}
|
||||
<h2>{{ shelf.name }}
|
||||
{% if shelf.size > shelf.books|length %}
|
||||
<small>(<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>)</small>
|
||||
{% endif %}
|
||||
</h2>
|
||||
<div class="covers-shelf {{ shelf.identifier }}">
|
||||
{% for book in shelf.books %}
|
||||
<div class="book-preview" onclick="show_compose(this)" id="book-{{ book.id }}">
|
||||
{% include 'snippets/book_cover.html' with book=book %}
|
||||
{% include 'snippets/shelve_button.html' with book=book %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if shelf.size > shelf.books.count %}
|
||||
<a href="/shelf/{{ user | username }}/{{ shelf.identifier }}">See all {{ shelf.size }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% else %}
|
||||
<h2>Reading Activity</h2>
|
||||
<p>Start a book!</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Recently Added Books</h2>
|
||||
{% for book in recent_books %}
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book.html' with book=book size="small" %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="feed">
|
||||
{% include 'snippets/tabs.html' with tabs=feed_tabs active_tab=active_tab %}
|
||||
|
||||
{% for activity in activities %}
|
||||
{% include 'snippets/status.html' with status=activity depth=1 description=True %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% for shelf in shelves %}
|
||||
{% for book in shelf.books %}
|
||||
<div class="compose-suggestion" id="compose-book-{{ book.id }}">
|
||||
<span class="close icon icon-close" onclick="hide_element(this)">
|
||||
<span class="hidden-text">Close</span>
|
||||
</span>
|
||||
<div class="content-container">
|
||||
{% include 'snippets/create_status.html' with book=book user=request.user %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
<div id="feed">
|
||||
<div class="content-container tabs">
|
||||
{% include 'snippets/tabs.html' with tabs=feed_tabs active_tab=active_tab %}
|
||||
</div>
|
||||
|
||||
{% for activity in activities %}
|
||||
<div class="content-container">
|
||||
{% include 'snippets/status.html' with status=activity %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<script src="/static/js/feed.js"></script>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,15 +2,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>FediReads</title>
|
||||
<title>BookWyrm</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link type="text/css" rel="stylesheet" href="/static/format.css">
|
||||
<link type="text/css" rel="stylesheet" href="/static/icons.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:title" content="BookWyrm">
|
||||
<meta name="og:title" content="BookWyrm">
|
||||
<meta name="twitter:description" content="Federated Social Reading">
|
||||
<meta name="og:description" content="Federated Social Reading">
|
||||
<meta name="twitter:creator" content="@tripofmice">
|
||||
|
@ -22,40 +23,43 @@
|
|||
|
||||
<div id="top-bar">
|
||||
<header>
|
||||
<div>
|
||||
<div id="branding"><a href="/">📚FediReads</a></div>
|
||||
<div id="actions">
|
||||
<div id="account">
|
||||
{% if request.user.is_authenticated %}
|
||||
<form name="logout" action="/logout/" method="post">
|
||||
{% csrf_token %}
|
||||
Welcome, {% include 'snippets/username.html' with user=request.user %}
|
||||
<input type="submit" value="Log out"></input>
|
||||
</form>
|
||||
{% else %}
|
||||
<form name="login" action="/login/" method="post">
|
||||
{% csrf_token %}
|
||||
{% for field in login_form %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<input type="submit" value="Log in"></input>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="search">
|
||||
<form action="/search/">
|
||||
<input type="text" name="q" placeholder="search"></input>
|
||||
<input type="submit" value="🔍"></input>
|
||||
</form>
|
||||
</div>
|
||||
{% if request.user.is_authenticated %}
|
||||
<div id="notification">
|
||||
<a href="/notifications">
|
||||
🔔 ({{ request.user | notification_count }})
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="branding"><a href="/"><img id="logo" src="/static/images/logo-small.png" alt="BookWyrm"></img></a></div>
|
||||
|
||||
<ul class="menu">
|
||||
<li><a href="/user/{{request.user.localname}}">Your shelves</a></li>
|
||||
<li><a href="/#feed">Updates</a></li>
|
||||
<li><a href="/books">Discover Books</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="actions">
|
||||
<div id="search">
|
||||
<form action="/search/">
|
||||
<input type="text" name="q" placeholder="Search for a book or user"></input>
|
||||
<button type="submit">
|
||||
<span class="icon icon-search">
|
||||
<span class="hidden-text">search</span>
|
||||
</span>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% if request.user.is_authenticated %}
|
||||
<div id="notifications">
|
||||
<a href="/notifications">
|
||||
<span class="icon icon-bell">
|
||||
<span class="hidden-text">Notitications</span>
|
||||
</span>
|
||||
{% if request.user|notification_count %}<span class="count">{{ request.user | notification_count }}</span>{% endif %}
|
||||
</a>
|
||||
</div>
|
||||
<div class="pulldown-container">
|
||||
{% include 'snippets/avatar.html' with user=request.user %}
|
||||
<ul class="pulldown">
|
||||
<li><a href="/user/{{ request.user }}">Your profile</a></li>
|
||||
<li><a href="/user-edit/">Settings</a></li>
|
||||
<li><a href="/logout/">Log out</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
</div>
|
||||
|
@ -65,6 +69,10 @@
|
|||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var csrf_token = {{ csrf_token }};
|
||||
</script>
|
||||
<script src="/static/js/shared.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1,14 +1,34 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
<div class="row">
|
||||
|
||||
<div class="content-container login">
|
||||
<h2>Create an Account</h2>
|
||||
<p><small>
|
||||
With a BookWyrm account, you can track and share your reading activity with
|
||||
friends here and on any other federated server, like Mastodon and PixelFed.
|
||||
</small></p>
|
||||
|
||||
<div>
|
||||
<form name="login" method="post">
|
||||
<form name="register" method="post" action="/register">
|
||||
{% csrf_token %}
|
||||
{{ register_form.as_p }}
|
||||
<button type="submit">Create account</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-container login">
|
||||
<h2>Log in</h2>
|
||||
<div>
|
||||
<form name="login" method="post" action="/user-login">
|
||||
{% csrf_token %}
|
||||
{{ login_form.as_p }}
|
||||
<button type="submit">Log in</button>
|
||||
</form>
|
||||
<a href="/register/">Create a new account</a>
|
||||
<p><small><a href="/reset-password">Forgot your password?</a></small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{% extends 'layout.html' %}
|
||||
{% block content %}
|
||||
<div id="content">
|
||||
<div>
|
||||
<form name="register" method="post">
|
||||
{% csrf_token %}
|
||||
{{ register_form.as_p }}
|
||||
<button type="submit">Create account</button>
|
||||
</form>
|
||||
|
||||
<a href="/login/">Log in with existing account</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,10 +0,0 @@
|
|||
{% load humanize %}
|
||||
<h2>
|
||||
{% include 'snippets/avatar.html' with user=activity.user %}
|
||||
{% include 'snippets/username.html' with user=activity.user %}
|
||||
{{ content | safe }}
|
||||
<span class="time-ago">
|
||||
<a href="{{ activity.absolute_id }}">{{ activity.published_date | naturaltime }}</a>
|
||||
</span>
|
||||
</h2>
|
||||
|
|
@ -15,4 +15,4 @@
|
|||
<blockquote>{{ book.description | description }}</blockquote>
|
||||
{% endif %}
|
||||
|
||||
{% include 'snippets/shelve-button.html' with book=book pulldown=shelf_pulldown %}
|
||||
{% include 'snippets/shelve_button.html' with book=book pulldown=shelf_pulldown %}
|
||||
|
|
6
fedireads/templates/snippets/covers_shelf.html
Normal file
6
fedireads/templates/snippets/covers_shelf.html
Normal file
|
@ -0,0 +1,6 @@
|
|||
{% load fr_display %}
|
||||
{% for book in books %}
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book.html' with rating=rating %}
|
||||
</div>
|
||||
{% endfor %}
|
32
fedireads/templates/snippets/create_status.html
Normal file
32
fedireads/templates/snippets/create_status.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{% load humanize %}
|
||||
{% load fr_display %}
|
||||
|
||||
<h2>
|
||||
{% include 'snippets/avatar.html' with user=user %}
|
||||
Your thoughts on
|
||||
<a href="/book/{{ book.openlibrary_key }}">{{ book.title }}</a>
|
||||
by {% include 'snippets/authors.html' with book=book %}
|
||||
</h2>
|
||||
|
||||
<div class="tabs secondary">
|
||||
<div class="tab active">
|
||||
Review
|
||||
</div>
|
||||
<div class="tab">
|
||||
Comment
|
||||
</div>
|
||||
<div class="tab">
|
||||
Quote
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book_cover.html' with book=book %}
|
||||
<form class="review-form" name="review" action="/review/" method="post">
|
||||
{% csrf_token %}
|
||||
{# TODO: this shouldn't use the openlibrary key #}
|
||||
<input type="hidden" name="book" value="{{ book.openlibrary_key }}"></input>
|
||||
{{ review_form.as_p }}
|
||||
<button type="submit">Post review</button>
|
||||
</form>
|
||||
</div>
|
|
@ -5,7 +5,7 @@
|
|||
{% csrf_token %}
|
||||
<input type="hidden" name="book" value="{{ book.id }}"></input>
|
||||
<input type="hidden" name="shelf" value="{% shelve_button_identifier book %}"></input>
|
||||
<button type="submit">{% shelve_button_text book %}</button>
|
||||
<button class="secondary" type="submit">{% shelve_button_text book %}</button>
|
||||
</form>
|
||||
|
||||
{% else %}
|
||||
|
@ -21,7 +21,7 @@
|
|||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit">Shelve</button>
|
||||
<button class="secondary" type="submit">Shelve</button>
|
||||
</form>
|
||||
|
||||
{% endif %}
|
|
@ -1,21 +1,48 @@
|
|||
{% load humanize %}
|
||||
{% load fr_display %}
|
||||
<div class="post {{ status.status_type|lower }} depth-{{ depth }} {% if main %}main{% else %}reply{% endif %}">
|
||||
{% include 'snippets/activity_banner.html' with activity=status %}
|
||||
<h2>
|
||||
{% include 'snippets/avatar.html' with user=status.user %}
|
||||
{% include 'snippets/username.html' with user=status.user %}
|
||||
{% if status.status_type == 'Update' %}
|
||||
{{ status.content | safe }}
|
||||
{% elif status.status_type == 'Review' %}
|
||||
reviewed {{ status.book.title }}
|
||||
{% elif status.reply_parent %}
|
||||
{% with parent_status=status|parent %}
|
||||
replied to {% include 'snippets/username.html' with user=parent_status.user possessive=True %} <a href="{{parent_status.absolute_id }}">{{ parent_status.status_type|lower }}</a>
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
<span class="time-ago">
|
||||
<a href="{{ status.absolute_id }}">{{ status.published_date | naturaltime }}</a>
|
||||
</span>
|
||||
</h2>
|
||||
|
||||
{% if not hide_book and status.mention_books.count %}
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book.html' with book=status.mention_books.first %}
|
||||
{% if status.status_type == 'Review' %}
|
||||
{% include 'snippets/book.html' with book=status.mention_books.first %}
|
||||
{% else %}
|
||||
{% include 'snippets/book.html' with book=status.mention_books.first description=True %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not hide_book and status.book%}
|
||||
<div class="book-preview">
|
||||
{% include 'snippets/book.html' with book=status.book %}
|
||||
{% if status.status_type == 'Review' %}
|
||||
{% include 'snippets/book.html' with book=status.book %}
|
||||
{% else %}
|
||||
{% include 'snippets/book.html' with book=status.book description=True %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if status.status_type == 'Review' %}<h4>{{ status.name }}
|
||||
<small>{{ status.rating | stars }} stars, by {% include 'snippets/username.html' with user=status.user %}</small>
|
||||
</h4>{% endif %}
|
||||
{% if status.status_type != 'Update' %}
|
||||
<blockquote>{{ status.content | safe }}</blockquote>
|
||||
{% endif %}
|
||||
{% if not max_depth and status.reply_parent or status|replies %}<p><a href="{{ status.absolute_id }}">Thread</a>{% endif %}
|
||||
{% include 'snippets/interaction.html' with activity=status %}
|
||||
</div>
|
||||
{% include 'snippets/interaction.html' with activity=status %}
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<div class="tabs">
|
||||
{% for tab in tabs %}
|
||||
<div class="tab {% if tab == active_tab %}active{% endif %}">
|
||||
<a href="{{ path }}/{{ tab }}">{{ tab }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for tab in tabs %}
|
||||
<div class="tab {% if tab.id == active_tab %}active{% endif %}">
|
||||
<a href="{{ path }}/{{ tab.id }}">{{ tab.display }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
{% load fr_display %}
|
||||
<a href="/user/{{ user | username }}" class="user">{% if user.name %}{{ user.name }}{% else %}{{ user | username }}{% endif %}</a> {% if show_full and user.name or show_full and user.localname %} ({{ user.username }}){% endif %}
|
||||
<a href="/user/{{ user | username }}" class="user">{% if user.name %}{{ user.name }}{% else %}{{ user | username }}{% endif %}</a>{% if possessive %}'s{% endif %}{% if show_full and user.name or show_full and user.localname %} ({{ user.username }}){% endif %}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
{% if is_self %}
|
||||
<div class="interaction">
|
||||
<a href="/edit_profile_page/">Edit profile</a>
|
||||
<a href="/user-edit/">Edit profile</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ from django.urls import path, re_path
|
|||
from fedireads import incoming, outgoing, views, settings, wellknown
|
||||
from fedireads import view_actions as actions
|
||||
|
||||
username_regex = r'(?P<username>[\w@\-_]+)'
|
||||
username_regex = r'(?P<username>[\w@\-_\.]+)'
|
||||
localname_regex = r'(?P<username>[\w\-_]+)'
|
||||
user_path = r'^user/%s' % username_regex
|
||||
local_user_path = r'^user/%s' % localname_regex
|
||||
|
@ -28,9 +28,7 @@ urlpatterns = [
|
|||
# TODO: re_path(r'^.well-known/host-meta/?$', incoming.host_meta),
|
||||
|
||||
# ui views
|
||||
re_path(r'^register/?$', views.register),
|
||||
re_path(r'^login/?$', views.user_login),
|
||||
re_path(r'^logout/?$', views.user_logout),
|
||||
re_path(r'^login/?$', views.login_page),
|
||||
|
||||
# should return a ui view or activitypub json blob as requested
|
||||
path('', views.home),
|
||||
|
@ -40,7 +38,7 @@ urlpatterns = [
|
|||
# users
|
||||
re_path(r'%s/?$' % user_path, views.user_page),
|
||||
re_path(r'%s\.json$' % local_user_path, views.user_page),
|
||||
re_path(r'edit_profile_page/?$', views.edit_profile_page),
|
||||
re_path(r'user-edit/?$', views.edit_profile_page),
|
||||
re_path(r'%s/followers/?$' % local_user_path, views.followers_page),
|
||||
re_path(r'%s/followers.json$' % local_user_path, views.followers_page),
|
||||
re_path(r'%s/following/?$' % local_user_path, views.following_page),
|
||||
|
@ -61,6 +59,9 @@ urlpatterns = [
|
|||
re_path(r'^shelf/%s/(?P<shelf_identifier>[\w-]+)/?$' % username_regex, views.shelf_page),
|
||||
|
||||
# internal action endpoints
|
||||
re_path(r'^logout/?$', actions.user_logout),
|
||||
re_path(r'^user-login/?$', actions.user_login),
|
||||
re_path(r'^register/?$', actions.register),
|
||||
re_path(r'^review/?$', actions.review),
|
||||
re_path(r'^tag/?$', actions.tag),
|
||||
re_path(r'^untag/?$', actions.untag),
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
''' views for actions you can take in the application '''
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.shortcuts import redirect
|
||||
|
@ -6,9 +7,54 @@ from django.template.response import TemplateResponse
|
|||
import re
|
||||
|
||||
from fedireads import forms, models, books_manager, outgoing
|
||||
from fedireads.settings import DOMAIN
|
||||
from fedireads.views import get_user_from_username
|
||||
|
||||
|
||||
def user_login(request):
|
||||
''' authenticate user login '''
|
||||
if request.method == 'GET':
|
||||
return redirect('/login')
|
||||
|
||||
form = forms.LoginForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return TemplateResponse(request, 'login.html', {'login_form': form})
|
||||
|
||||
username = form.data['username']
|
||||
username = '%s@%s' % (username, DOMAIN)
|
||||
password = form.data['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', {'login_form': form})
|
||||
|
||||
|
||||
def register(request):
|
||||
''' join the server '''
|
||||
if request.method == 'GET':
|
||||
return redirect('/login')
|
||||
|
||||
form = forms.RegisterForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return redirect('/register/')
|
||||
|
||||
username = form.data['username']
|
||||
email = form.data['email']
|
||||
password = form.data['password']
|
||||
|
||||
user = models.User.objects.create_user(username, email, password)
|
||||
login(request, user)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
@login_required
|
||||
def user_logout(request):
|
||||
''' done with this place! outa here! '''
|
||||
logout(request)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
@login_required
|
||||
def edit_profile(request):
|
||||
''' les get fancy with images '''
|
||||
|
@ -23,7 +69,8 @@ def edit_profile(request):
|
|||
if 'avatar' in form.files:
|
||||
request.user.avatar = form.files['avatar']
|
||||
request.user.summary = form.data['summary']
|
||||
request.user.manually_approves_followers = form.cleaned_data['manually_approves_followers']
|
||||
request.user.manually_approves_followers = \
|
||||
form.cleaned_data['manually_approves_followers']
|
||||
request.user.save()
|
||||
return redirect('/user/%s' % request.user.localname)
|
||||
|
||||
|
@ -160,11 +207,14 @@ def search(request):
|
|||
|
||||
@login_required
|
||||
def clear_notifications(request):
|
||||
''' permanently delete notification for user '''
|
||||
request.user.notification_set.filter(read=True).delete()
|
||||
return redirect('/notifications')
|
||||
|
||||
|
||||
@login_required
|
||||
def accept_follow_request(request):
|
||||
''' a user accepts a follow request '''
|
||||
username = request.POST['user']
|
||||
try:
|
||||
requester = get_user_from_username(username)
|
||||
|
@ -172,7 +222,10 @@ def accept_follow_request(request):
|
|||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
follow_request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=request.user)
|
||||
follow_request = models.UserFollowRequest.objects.get(
|
||||
user_subject=requester,
|
||||
user_object=request.user
|
||||
)
|
||||
except models.UserFollowRequest.DoesNotExist:
|
||||
# Request already dealt with.
|
||||
pass
|
||||
|
@ -181,8 +234,10 @@ def accept_follow_request(request):
|
|||
|
||||
return redirect('/user/%s' % request.user.localname)
|
||||
|
||||
|
||||
@login_required
|
||||
def delete_follow_request(request):
|
||||
''' a user rejects a follow request '''
|
||||
username = request.POST['user']
|
||||
try:
|
||||
requester = get_user_from_username(username)
|
||||
|
@ -190,9 +245,13 @@ def delete_follow_request(request):
|
|||
return HttpResponseBadRequest()
|
||||
|
||||
try:
|
||||
follow_request = models.UserFollowRequest.objects.get(user_subject=requester, user_object=request.user)
|
||||
follow_request = models.UserFollowRequest.objects.get(
|
||||
user_subject=requester,
|
||||
user_object=request.user
|
||||
)
|
||||
except models.UserFollowRequest.DoesNotExist:
|
||||
return HttpResponseBadRequest()
|
||||
|
||||
outgoing.handle_outgoing_reject(requester, request.user, follow_request)
|
||||
return redirect('/user/%s' % request.user.localname)
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
''' views for pages you can go to in the application '''
|
||||
from django.contrib.auth import authenticate, login, logout
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Avg, Q
|
||||
from django.http import HttpResponseBadRequest, HttpResponseNotFound, \
|
||||
|
@ -10,7 +9,6 @@ from django.views.decorators.csrf import csrf_exempt
|
|||
|
||||
from fedireads import activitypub
|
||||
from fedireads import forms, models, books_manager
|
||||
from fedireads.settings import DOMAIN
|
||||
|
||||
|
||||
def get_user_from_username(username):
|
||||
|
@ -22,7 +20,6 @@ def get_user_from_username(username):
|
|||
return user
|
||||
|
||||
|
||||
|
||||
def is_api_request(request):
|
||||
''' check whether a request is asking for html or data '''
|
||||
# TODO: this should probably be the full content type? maybe?
|
||||
|
@ -40,28 +37,44 @@ def home(request):
|
|||
def home_tab(request, tab):
|
||||
''' user's homepage with activity feed '''
|
||||
shelves = []
|
||||
for identifier in ['reading', 'to-read']:
|
||||
book_count = 6
|
||||
for (identifier, count) in [('reading', 3), ('read', 1), ('to-read', 3)]:
|
||||
if book_count <= 0:
|
||||
break
|
||||
shelf = models.Shelf.objects.get(
|
||||
user=request.user,
|
||||
identifier=identifier,
|
||||
)
|
||||
if not shelf.books.count():
|
||||
continue
|
||||
books = models.ShelfBook.objects.filter(
|
||||
shelf=shelf,
|
||||
).order_by(
|
||||
'-updated_date'
|
||||
)[:count]
|
||||
|
||||
book_count -= len(books)
|
||||
|
||||
shelves.append({
|
||||
'name': shelf.name,
|
||||
'identifier': shelf.identifier,
|
||||
'books': shelf.books.all()[:3],
|
||||
'books': [b.book for b in books],
|
||||
'size': shelf.books.count(),
|
||||
})
|
||||
# books new to the instance, for discovery
|
||||
if book_count > 0:
|
||||
shelves.append({
|
||||
'name': 'Recently added',
|
||||
'identifier': None,
|
||||
'books': models.Book.objects.order_by(
|
||||
'-created_date'
|
||||
)[:book_count],
|
||||
'count': book_count,
|
||||
})
|
||||
|
||||
# allows us to check if a user has shelved a book
|
||||
user_books = models.Book.objects.filter(shelves__user=request.user).all()
|
||||
|
||||
# books new to the instance, for discovery
|
||||
recent_books = models.Book.objects.order_by(
|
||||
'-created_date'
|
||||
)[:5]
|
||||
|
||||
# status updates for your follow network
|
||||
following = models.User.objects.filter(
|
||||
Q(followers=request.user) | Q(id=request.user.id)
|
||||
|
@ -89,65 +102,27 @@ def home_tab(request, tab):
|
|||
data = {
|
||||
'user': request.user,
|
||||
'shelves': shelves,
|
||||
'recent_books': recent_books,
|
||||
'user_books': user_books,
|
||||
'activities': activities,
|
||||
'feed_tabs': ['home', 'local', 'federated'],
|
||||
'feed_tabs': [
|
||||
{'id': 'home', 'display': 'Home'},
|
||||
{'id': 'local', 'display': 'Local'},
|
||||
{'id': 'federated', 'display': 'Federated'}
|
||||
],
|
||||
'active_tab': tab,
|
||||
'review_form': forms.ReviewForm(),
|
||||
}
|
||||
return TemplateResponse(request, 'feed.html', data)
|
||||
|
||||
|
||||
def user_login(request):
|
||||
def login_page(request):
|
||||
''' authentication '''
|
||||
# send user to the login page
|
||||
if request.method == 'GET':
|
||||
form = forms.LoginForm()
|
||||
return TemplateResponse(request, 'login.html', {'login_form': form})
|
||||
|
||||
# authenticate user
|
||||
form = forms.LoginForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return TemplateResponse(request, 'login.html', {'login_form': form})
|
||||
|
||||
username = form.data['username']
|
||||
username = '%s@%s' % (username, DOMAIN)
|
||||
password = form.data['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', {'login_form': form})
|
||||
|
||||
|
||||
@login_required
|
||||
def user_logout(request):
|
||||
''' done with this place! outa here! '''
|
||||
logout(request)
|
||||
return redirect('/')
|
||||
|
||||
|
||||
def register(request):
|
||||
''' join the server '''
|
||||
if request.method == 'GET':
|
||||
form = forms.RegisterForm()
|
||||
return TemplateResponse(
|
||||
request,
|
||||
'register.html',
|
||||
{'register_form': form}
|
||||
)
|
||||
|
||||
form = forms.RegisterForm(request.POST)
|
||||
if not form.is_valid():
|
||||
return redirect('/register/')
|
||||
|
||||
username = form.data['username']
|
||||
email = form.data['email']
|
||||
password = form.data['password']
|
||||
|
||||
user = models.User.objects.create_user(username, email, password)
|
||||
login(request, user)
|
||||
return redirect('/')
|
||||
data = {
|
||||
'login_form': forms.LoginForm(),
|
||||
'register_form': forms.RegisterForm(),
|
||||
}
|
||||
return TemplateResponse(request, 'login.html', data)
|
||||
|
||||
|
||||
@login_required
|
||||
|
|
Loading…
Reference in a new issue