├── backend ├── __init__.py ├── migrations │ ├── __init__.py │ ├── 0003_auto_20200811_1833.py │ ├── 0005_auto_20200812_1156.py │ ├── 0006_auto_20200902_1957.py │ ├── 0004_auto_20200812_1055.py │ ├── 0002_auto_20200810_0722.py │ ├── 0007_auto_20200910_2113.py │ └── 0001_initial.py ├── tests.py ├── admin.py ├── apps.py ├── static │ ├── icons │ │ ├── upload.png │ │ ├── arrow-down.svg │ │ ├── user.svg │ │ ├── sign-in-alt.svg │ │ ├── sign-out-alt.svg │ │ ├── pencil-alt.svg │ │ ├── trash-alt.svg │ │ ├── location.svg │ │ ├── thumbs-up.svg │ │ ├── thumbs-down.svg │ │ └── comment.svg │ ├── registration │ │ └── style.css │ └── social │ │ ├── style.css │ │ └── basescript.js ├── views │ ├── __init__.py │ ├── info.py │ ├── profile.py │ ├── authentication.py │ ├── follow.py │ └── post.py ├── permissions.py ├── templates │ ├── social │ │ ├── following.html │ │ └── followers.html │ └── registration │ │ ├── base.html │ │ ├── signup.html │ │ └── login.html ├── forms.py ├── urls.py ├── serializers.py └── models.py ├── frontend ├── __init__.py ├── migrations │ └── __init__.py ├── src │ ├── index.js │ └── components │ │ ├── Comments.js │ │ ├── Index.js │ │ ├── App.js │ │ ├── UserList.js │ │ ├── Sidebar.js │ │ ├── App.css │ │ ├── Profile.js │ │ ├── Settings.js │ │ ├── Post.js │ │ └── Misc.js ├── models.py ├── admin.py ├── tests.py ├── .babelrc ├── apps.py ├── static │ ├── icons │ │ ├── upload.png │ │ ├── arrow-down.svg │ │ ├── user.svg │ │ ├── sign-in-alt.svg │ │ ├── sign-out-alt.svg │ │ ├── pencil-alt.svg │ │ ├── trash-alt.svg │ │ ├── location.svg │ │ ├── thumbs-up.svg │ │ ├── thumbs-down.svg │ │ └── comment.svg │ ├── registration │ │ └── style.css │ └── social │ │ └── style.css ├── templates │ └── frontend │ │ └── app.html ├── urls.py ├── webpack.config.js ├── views.py └── package.json ├── socialproject ├── __init__.py ├── wsgi.py ├── urls.py └── settings.py ├── requirements.txt ├── manage.py └── .gitignore /backend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /socialproject/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import App from "./components/App"; -------------------------------------------------------------------------------- /backend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /backend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /frontend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /frontend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", "@babel/preset-react" 4 | ] 5 | } -------------------------------------------------------------------------------- /backend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class BackendConfig(AppConfig): 5 | name = 'backend' 6 | -------------------------------------------------------------------------------- /backend/static/icons/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxknivets/drf-social-network/HEAD/backend/static/icons/upload.png -------------------------------------------------------------------------------- /frontend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FrontendConfig(AppConfig): 5 | name = 'frontend' 6 | -------------------------------------------------------------------------------- /frontend/static/icons/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maxknivets/drf-social-network/HEAD/frontend/static/icons/upload.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django==2.2.19 2 | djangorestframework==3.12.4 3 | django-cors-headers==3.7.0 4 | django-currentuser==0.5.2 5 | Pillow==8.2.0 -------------------------------------------------------------------------------- /backend/views/__init__.py: -------------------------------------------------------------------------------- 1 | from .authentication import * 2 | from .post import * 3 | from .follow import * 4 | from .info import * 5 | from .profile import * -------------------------------------------------------------------------------- /backend/static/registration/style.css: -------------------------------------------------------------------------------- 1 | span { 2 | text-align: center; 3 | font-size: 2em; 4 | display: block; 5 | } 6 | 7 | ul { 8 | width: 70%; 9 | margin: auto; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/static/registration/style.css: -------------------------------------------------------------------------------- 1 | span { 2 | text-align: center; 3 | font-size: 2em; 4 | display: block; 5 | } 6 | 7 | ul { 8 | width: 70%; 9 | margin: auto; 10 | } 11 | -------------------------------------------------------------------------------- /backend/permissions.py: -------------------------------------------------------------------------------- 1 | from rest_framework import permissions 2 | 3 | 4 | class IsPostOwner(permissions.BasePermission): 5 | 6 | def has_object_permission(self, request, view, post): 7 | if request.method in permissions.SAFE_METHODS: 8 | return True 9 | return post.posted_by == request.user -------------------------------------------------------------------------------- /backend/static/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/icons/sign-in-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/sign-in-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/icons/sign-out-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/sign-out-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /socialproject/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for root project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "socialproject.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/migrations/0003_auto_20200811_1833.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-08-11 15:33 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0002_auto_20200810_0722'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='postrate', 15 | name='liked', 16 | field=models.BooleanField(null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/templates/social/following.html: -------------------------------------------------------------------------------- 1 | 2 | {{ theuser.username }}'s follows 3 | 4 | {% load staticfiles %} 5 | 6 | {% if following_list %} 7 |
    8 | {% for following in following_list %} 9 |
  1. {{ following.user }}
  2. 10 | {% endfor %} 11 |
12 | {% else %} 13 | Looks like the user doesn't follow anyone! 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /backend/static/icons/pencil-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/pencil-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.contrib.auth.models import User 4 | from backend.models import Post 5 | 6 | class Sign_up_form(UserCreationForm): 7 | 8 | def __init__(self, *args, **kwargs): 9 | super(Sign_up_form, self).__init__(*args, **kwargs) 10 | 11 | for fieldname in ['username', 'password1', 'password2',]: 12 | self.fields[fieldname].help_text = None 13 | 14 | class Meta: 15 | model = User 16 | fields = ('username', 'password1', 'password2') -------------------------------------------------------------------------------- /backend/templates/registration/base.html: -------------------------------------------------------------------------------- 1 | {% load static %} 2 | 3 | 4 | 5 | {% block title %}Default Title{% endblock %} 6 | 7 | {% block stylesheet %}{% endblock %} 8 | 9 | 10 |
11 | {% block content %} 12 | {% endblock %} 13 |
14 | 15 | {% block javascript %}{% endblock %} 16 | 17 | 18 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "socialproject.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /frontend/src/components/Comments.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import { Posts, QuerySinglePost } from './Post' 5 | 6 | import styles from './App.css'; 7 | 8 | export function Comments(props) { 9 | const postId = parseInt(props.match.params.id); 10 | 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /frontend/templates/frontend/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Home 8 | 9 | 10 |
11 | 12 | {% load static %} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/components/Index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import classnames from 'classnames' 4 | 5 | import { Posts } from './Post' 6 | import { TextForm } from './Misc' 7 | 8 | import styles from './App.css'; 9 | 10 | export function Index() { 11 | return ( 12 |
13 |
14 | Publish post

} formClass={'post-input'} buttonValue={'Publish'} /> 15 |
16 |
17 | 18 |
19 |
20 | ); 21 | } -------------------------------------------------------------------------------- /backend/static/icons/trash-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/trash-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.contrib import admin 3 | from django.contrib.auth import views as auth_views 4 | 5 | from .views import home, comments_page, profile_page, settings_page, followers_page, following_page 6 | from rest_framework.authtoken import views as rest_auth_views 7 | 8 | app_name = 'frontend' 9 | 10 | urlpatterns = [ 11 | path('', home, name='home'), 12 | path('settings/', settings_page, name='settings'), 13 | path('profile//', profile_page, name='profile'), 14 | path('post//comments/', comments_page, name='comments'), 15 | path('following//', following_page, name='following'), 16 | path('followers//', followers_page, name='followers'), 17 | ] -------------------------------------------------------------------------------- /backend/templates/social/followers.html: -------------------------------------------------------------------------------- 1 | 2 | {{ theuser.username }}'s followers 3 | 4 | {% load staticfiles %} 5 | 6 | {% if followers_list %} 7 |
    8 | {% for followers in followers_list %} 9 |
  1. {{ followers.is_followed_by }}
  2. 10 | {% endfor %} 11 |
12 | {% else %} 13 | {% if user != theuser %} Looks like the user doesn't have any followers :(
Be the first to follow! {% else %} Looks like you don't have any followers :(
Write some posts to draw other's attention! {% endif %} 14 | {% endif %} 15 | -------------------------------------------------------------------------------- /backend/migrations/0005_auto_20200812_1156.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-08-12 08:56 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0004_auto_20200812_1055'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='post', 15 | old_name='post_text', 16 | new_name='text', 17 | ), 18 | migrations.AddField( 19 | model_name='post', 20 | name='in_reply_to_post', 21 | field=models.IntegerField(blank=True, null=True), 22 | ), 23 | migrations.DeleteModel( 24 | name='Comment', 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/views/info.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render, redirect, get_object_or_404 3 | from django.http import JsonResponse 4 | from backend.models import User, Post, Follower, Profile 5 | import datetime 6 | 7 | @login_required 8 | def profile(request, user_id): 9 | user = get_object_or_404(User, pk = user_id) 10 | users_posts = Post.objects.filter(user = user) 11 | return render(request, 'social/user.html', { 12 | 'user_info': user, 13 | 'request_user': request.user, 14 | 'latest_posts_list': users_posts, 15 | 'latest_post': users_posts, 16 | }) 17 | 18 | @login_required 19 | def changeinfo(request): 20 | return render(request, 'social/change_info.html', {}) -------------------------------------------------------------------------------- /backend/migrations/0006_auto_20200902_1957.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-09-02 16:57 2 | 3 | from django.conf import settings 4 | from django.db import migrations 5 | import django.db.models.deletion 6 | import django_currentuser.db.models.fields 7 | import django_currentuser.middleware 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('backend', '0005_auto_20200812_1156'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AlterField( 18 | model_name='post', 19 | name='posted_by', 20 | field=django_currentuser.db.models.fields.CurrentUserField(default=django_currentuser.middleware.get_current_authenticated_user, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/static/icons/location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /frontend/static/icons/location.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.js$/, 6 | exclude: /node_modules/, 7 | use: { 8 | loader: "babel-loader" 9 | } 10 | }, 11 | { 12 | test: /\.css$/, 13 | loader: 'style-loader' 14 | }, 15 | { 16 | test: /\.css$/, 17 | loader: 'css-loader', 18 | options: { 19 | modules: true, 20 | //localIdentName: '[name]__[local]___[hash:base64:5]' 21 | } 22 | } 23 | ], 24 | }, 25 | mode: 'development', 26 | output: { 27 | publicPath: 'http://localhost:9000/static/', 28 | }, 29 | devServer: { 30 | contentBase: './static', 31 | port: 9000, 32 | headers: { 33 | 'Access-Control-Allow-Origin': '*' 34 | }, 35 | compress: true, 36 | hot: true 37 | }, 38 | }; -------------------------------------------------------------------------------- /backend/migrations/0004_auto_20200812_1055.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-08-12 07:55 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('backend', '0003_auto_20200811_1833'), 10 | ] 11 | 12 | operations = [ 13 | migrations.RenameField( 14 | model_name='comment', 15 | old_name='comment', 16 | new_name='comment_text', 17 | ), 18 | migrations.RenameField( 19 | model_name='post', 20 | old_name='user', 21 | new_name='posted_by', 22 | ), 23 | migrations.RemoveField( 24 | model_name='comment', 25 | name='post_date', 26 | ), 27 | migrations.AddField( 28 | model_name='comment', 29 | name='pub_date', 30 | field=models.DateTimeField(auto_now=True, verbose_name='Publication Date'), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/templates/registration/signup.html: -------------------------------------------------------------------------------- 1 | Sign up 2 | {% extends 'registration/base.html' %} 3 | {% load staticfiles %} 4 | 5 | 6 | {% block javascript %} 7 | 26 | {% endblock %} 27 | 28 | {% block content %} 29 |

Sign up

30 |
31 | {% csrf_token %} 32 | {{ form.as_p }} 33 | 34 |
35 | Have an account? Log in!
36 | {% endblock %} 37 | -------------------------------------------------------------------------------- /backend/views/profile.py: -------------------------------------------------------------------------------- 1 | from backend.models import Profile 2 | from backend.serializers import ProfileSerializer 3 | from django.http import JsonResponse 4 | 5 | from rest_framework import mixins, permissions, generics 6 | 7 | class ProfileViewSet(mixins.RetrieveModelMixin, generics.GenericAPIView): 8 | queryset = Profile.objects.all() 9 | serializer_class = ProfileSerializer 10 | permission_classes = [permissions.IsAuthenticated] 11 | 12 | def get(self, request, *args, **kwargs): 13 | return self.retrieve(request, *args, **kwargs) 14 | 15 | def patch(self, request): # change this to use the patch mixin 16 | profile = Profile.objects.filter(user = request.user).first() 17 | profile.first_name = request.data['first_name'] 18 | profile.last_name = request.data['last_name'] 19 | profile.bio = request.data['bio'] 20 | profile.location = request.data['location'] 21 | profile.save() 22 | return JsonResponse({"response": "change successful"}) -------------------------------------------------------------------------------- /backend/static/icons/thumbs-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/thumbs-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/static/icons/thumbs-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/static/icons/thumbs-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/views.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | from django.shortcuts import render 3 | 4 | @login_required(login_url="/login/", redirect_field_name=None) 5 | def home(request): 6 | return render(request, 'frontend/app.html', {}) 7 | 8 | @login_required(login_url="/login/", redirect_field_name=None) 9 | def comments_page(request, id): 10 | return render(request, 'frontend/app.html', {}) 11 | 12 | @login_required(login_url="/login/", redirect_field_name=None) 13 | def profile_page(request, id): 14 | return render(request, 'frontend/app.html', {}) 15 | 16 | @login_required(login_url="/login/", redirect_field_name=None) 17 | def settings_page(request): 18 | return render(request, 'frontend/app.html', {}) 19 | 20 | @login_required(login_url="/login/", redirect_field_name=None) 21 | def followers_page(request, id): 22 | return render(request, 'frontend/app.html', {}) 23 | 24 | @login_required(login_url="/login/", redirect_field_name=None) 25 | def following_page(request, id): 26 | return render(request, 'frontend/app.html', {}) -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development ./src/index.js --output ./static/frontend/main.js", 8 | "build": "webpack --mode production ./src/index.js --output ./static/frontend/main.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "devDependencies": { 14 | "@babel/core": "^7.13.14", 15 | "@babel/preset-env": "^7.13.12", 16 | "@babel/preset-react": "^7.13.13", 17 | "babel-loader": "^8.2.2", 18 | "css-loader": "^4.3.0", 19 | "jquery": "^3.6.0", 20 | "js-cookie": "^2.2.1", 21 | "react": "^16.14.0", 22 | "react-dom": "^16.14.0", 23 | "style-loader": "^1.3.0", 24 | "webpack": "^4.46.0", 25 | "webpack-cli": "^3.3.12", 26 | "webpack-dev-server": "^3.11.2" 27 | }, 28 | "dependencies": { 29 | "classnames": "^2.3.1", 30 | "js-cookie": "^2.2.1", 31 | "prop-types": "^15.7.2", 32 | "react-router": "^5.2.0", 33 | "react-router-dom": "^5.2.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/templates/registration/login.html: -------------------------------------------------------------------------------- 1 | 2 | {% block title %} Log in {% endblock %} 3 | 4 | 5 | {% load staticfiles %} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | {% block content %} 14 |
15 | {% csrf_token %} 16 | {{ form.as_p }} 17 | 18 |
No account? Create one!
19 |
20 | {% endblock %} 21 | 22 | -------------------------------------------------------------------------------- /backend/static/icons/comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /frontend/static/icons/comment.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svg Vector Icons : http://www.onlinewebfonts.com/icon 6 | 7 | -------------------------------------------------------------------------------- /frontend/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM, { render } from 'react-dom'; 3 | import { BrowserRouter, Route, Switch } from 'react-router-dom'; 4 | 5 | import { Index } from './Index'; 6 | import { Comments } from './Comments'; 7 | import { Sidebar } from './Sidebar'; 8 | import { Settings } from './Settings'; 9 | import { Profile } from './Profile'; 10 | import { UserList } from './UserList'; 11 | 12 | function App() { 13 | return ( 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | ()} exact /> 23 | ()} exact /> 24 | 25 | 26 | 27 |
28 |
29 | ) 30 | } 31 | 32 | export default App; 33 | 34 | const container = document.getElementById('app'); 35 | render(, container); -------------------------------------------------------------------------------- /socialproject/urls.py: -------------------------------------------------------------------------------- 1 | """root URL Configuration 2 | 3 | The `urlpatterns` list routes URLs to views. For more information please see: 4 | https://docs.djangoproject.com/en/2.0/topics/http/urls/ 5 | Examples: 6 | Function views 7 | 1. Add an import: from my_app import views 8 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 9 | Class-based views 10 | 1. Add an import: from other_app.views import Home 11 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 12 | Including another URLconf 13 | 1. Import the include() function: from django.urls import include, path 14 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 15 | """ 16 | 17 | from django.urls import include, path 18 | from django.conf.urls import include 19 | from django.contrib.staticfiles.urls import static 20 | from django.contrib.staticfiles.urls import staticfiles_urlpatterns 21 | from rest_framework import routers 22 | from . import settings 23 | 24 | urlpatterns = [ 25 | path('', include('backend.urls')), 26 | path('', include('frontend.urls')), 27 | ] 28 | 29 | urlpatterns += staticfiles_urlpatterns() 30 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 31 | urlpatterns += [path('api-auth/', include('rest_framework.urls')),] 32 | 33 | -------------------------------------------------------------------------------- /backend/migrations/0002_auto_20200810_0722.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-08-10 04:22 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('backend', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='PostRate', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('liked', models.BooleanField()), 21 | ('rated_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 22 | ('rated_post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.Post')), 23 | ], 24 | ), 25 | migrations.RemoveField( 26 | model_name='like', 27 | name='liked_by', 28 | ), 29 | migrations.RemoveField( 30 | model_name='like', 31 | name='liked_post', 32 | ), 33 | migrations.DeleteModel( 34 | name='Dislike', 35 | ), 36 | migrations.DeleteModel( 37 | name='Like', 38 | ), 39 | ] 40 | -------------------------------------------------------------------------------- /backend/views/authentication.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth import authenticate, login, logout 2 | from django.contrib.auth.forms import UserCreationForm 3 | from django.shortcuts import render, redirect 4 | from django.http import JsonResponse 5 | from backend.models import User, Profile 6 | from rest_framework.authtoken.models import Token 7 | from ..forms import Sign_up_form 8 | 9 | def signup(request): 10 | if request.user.is_authenticated: 11 | return redirect('/') 12 | if request.method == 'POST': 13 | form = Sign_up_form(request.POST) 14 | if form.is_valid(): 15 | form.save() 16 | username = form.cleaned_data.get('username') 17 | raw_password = form.cleaned_data.get('password1') 18 | user = authenticate(username = username, password = raw_password) 19 | profile = Profile(user = user) 20 | profile.save() 21 | login(request, user) 22 | return redirect('/') 23 | else: 24 | form = Sign_up_form() 25 | return render(request, 'registration/signup.html', {'form': form}) 26 | 27 | def logout(request): 28 | logout(request) 29 | return redirect('/login') 30 | 31 | # Ajax endpoint to check if the user is already taken, only used in the signup template 32 | def validate_username(request): 33 | username = request.GET.get('username', None) 34 | data = { 35 | 'is_taken': User.objects.filter(username__iexact = username).exists() 36 | } 37 | return JsonResponse(data) 38 | 39 | -------------------------------------------------------------------------------- /backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from django.contrib import admin 3 | from django.contrib.auth import views as auth_views 4 | from rest_framework.routers import SimpleRouter 5 | 6 | from . import views 7 | 8 | app_name = 'backend' 9 | 10 | router = SimpleRouter() 11 | router.register(r'api/post', views.PostViewSet) 12 | 13 | urlpatterns = [ 14 | # user logging and administration 15 | path('login/', auth_views.LoginView.as_view(redirect_authenticated_user=True), name='login'), 16 | path('logout/', auth_views.LogoutView.as_view(next_page='/login'), name='logout'), 17 | path('signup/', views.signup, name='signup'), 18 | path('ajax/validate_username/', views.validate_username, name='validate_username'), 19 | path('admin/', admin.site.urls, name='admin'), 20 | path('api-auth/', include('rest_framework.urls')), 21 | 22 | 23 | path('', include(router.urls)), 24 | path('api/post/rate/', views.PostRateViewSet.as_view(), name='rate'), 25 | path('api/post/rating//', views.PostRateViewSet.as_view(), name='rating'), 26 | path('api/post/retrieve-comments//', views.CommentList.as_view(), name='retrieve-comments'), 27 | 28 | path('api/profile/', views.ProfileViewSet.as_view(), name='profile-change'), 29 | path('api/profile//', views.ProfileViewSet.as_view(), name='profile-retrieve'), 30 | 31 | path('api/follow//', views.follow, name='follow'), 32 | path('api/following//', views.Following.as_view(), name='following'), 33 | path('api/followers//', views.Followers.as_view(), name='followers'), 34 | ] -------------------------------------------------------------------------------- /frontend/src/components/UserList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useState, useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Cookies from 'js-cookie'; 4 | import classnames from 'classnames' 5 | import styles from './App.css'; 6 | 7 | export function UserList(props) { 8 | const profileId = parseInt(props.match.params.id); 9 | const url = props.followers ? 'followers': 'following'; 10 | const [userList, setFollowersList] = useState( 11 | [{ 12 | "user": { 13 | "id": null, 14 | "username": null, 15 | }, 16 | "is_followed_by": { 17 | "id": null, 18 | "username": null, 19 | }, 20 | }]); 21 | 22 | useEffect(() => { 23 | fetch(`/api/${url}/${profileId}/`, { 24 | headers: { 25 | 'Content-Type': 'application/json', 26 | 'Authorization': Cookies.get('sessionid'), 27 | 'X-CSRFToken': Cookies.get('csrftoken') 28 | }, 29 | method: "GET", 30 | }) 31 | .then(response => { return response.json() }) 32 | .then(data => { setFollowersList(data) }) 33 | }, []) 34 | return ( 35 | 54 | ) 55 | } -------------------------------------------------------------------------------- /backend/views/follow.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.decorators import login_required 2 | 3 | from django.shortcuts import redirect, get_object_or_404 4 | from django.http import JsonResponse 5 | 6 | from backend.models import User, Post, Follower, Profile 7 | from backend.serializers import FollowerSerializer 8 | from rest_framework import generics, mixins, permissions 9 | 10 | @login_required 11 | def follow(request, pk): 12 | user = get_object_or_404(User, pk = pk) 13 | already_followed = Follower.objects.filter(user = user, is_followed_by = request.user).first() 14 | if not already_followed: 15 | new_follower = Follower(user = user, is_followed_by = request.user) 16 | new_follower.save() 17 | follower_count = Follower.objects.filter(user = user).count() 18 | return JsonResponse({'status': 'Following', 'count': follower_count}) 19 | else: 20 | already_followed.delete() 21 | follower_count = Follower.objects.filter(user = user).count() 22 | return JsonResponse({'status': 'Not following', 'count': follower_count}) 23 | return redirect('/') 24 | 25 | class Following(generics.ListCreateAPIView): 26 | serializer_class = FollowerSerializer 27 | permission_classes = [permissions.IsAuthenticated] 28 | 29 | def get_queryset(self): 30 | user = get_object_or_404(User, pk = self.kwargs["pk"]) 31 | return Follower.objects.filter(is_followed_by = user) 32 | 33 | class Followers(generics.ListCreateAPIView): 34 | queryset = Follower.objects.all() 35 | serializer_class = FollowerSerializer 36 | permission_classes = [permissions.IsAuthenticated] 37 | 38 | def get_queryset(self): 39 | user = get_object_or_404(User, pk = self.kwargs["pk"]) 40 | return Follower.objects.filter(user = user).exclude(is_followed_by = user) -------------------------------------------------------------------------------- /backend/migrations/0007_auto_20200910_2113.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-09-10 18:13 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django_currentuser.db.models.fields 7 | import django_currentuser.middleware 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('backend', '0006_auto_20200902_1957'), 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | model_name='profile', 19 | name='first_name', 20 | field=models.CharField(blank=True, max_length=100, null=True), 21 | ), 22 | migrations.AddField( 23 | model_name='profile', 24 | name='last_name', 25 | field=models.CharField(blank=True, max_length=100, null=True), 26 | ), 27 | migrations.AlterField( 28 | model_name='post', 29 | name='image', 30 | field=models.ImageField(null=True, upload_to='post-images'), 31 | ), 32 | migrations.AlterField( 33 | model_name='post', 34 | name='in_reply_to_post', 35 | field=models.IntegerField(null=True), 36 | ), 37 | migrations.AlterField( 38 | model_name='post', 39 | name='posted_by', 40 | field=django_currentuser.db.models.fields.CurrentUserField(default=django_currentuser.middleware.get_current_authenticated_user, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='posted_by', to=settings.AUTH_USER_MODEL), 41 | ), 42 | migrations.AlterField( 43 | model_name='profile', 44 | name='bio', 45 | field=models.CharField(blank=True, max_length=100, null=True), 46 | ), 47 | migrations.AlterField( 48 | model_name='profile', 49 | name='location', 50 | field=models.CharField(blank=True, max_length=100, null=True), 51 | ), 52 | migrations.AlterField( 53 | model_name='profile', 54 | name='profile_picture', 55 | field=models.ImageField(blank=True, null=True, upload_to='profile-pictures'), 56 | ), 57 | ] 58 | -------------------------------------------------------------------------------- /frontend/src/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import styles from './App.css'; 5 | import Cookies from 'js-cookie'; 6 | 7 | export function Sidebar() { 8 | 9 | const [currentAuthenticatedUserId, setCurrentAuthenticatedUserId] = useState(1); 10 | 11 | useEffect(() => { 12 | fetch(`/api/get-current-authenticated-user-id/`, { 13 | headers: { 14 | 'Content-Type': 'application/json', 15 | 'Authorization': Cookies.get('sessionid'), 16 | }, 17 | }) 18 | .then(response => { return response.json() }) 19 | .then(data => { setCurrentAuthenticatedUserId(data.current_authenticated_user_id) }) 20 | }, []) 21 | 22 | return ( 23 |
24 |
25 |
26 |
27 |
28 |
29 | Home 30 |
31 | Your profile 32 |
33 | Settings 34 |
35 | Log out 36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 |
55 |
56 | ) 57 | } -------------------------------------------------------------------------------- /frontend/src/components/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: #1b1919; 4 | } 5 | 6 | ul { 7 | list-style-type: none; 8 | } 9 | 10 | textarea { 11 | resize: none !important; 12 | padding: 10px 25px; 13 | border: 1.5px solid #ccc; 14 | border-radius: 4px; 15 | } 16 | 17 | .hidden { 18 | display: none; 19 | } 20 | 21 | .center{ 22 | margin: 0 auto; 23 | text-align: center; 24 | } 25 | 26 | .homecenter { 27 | display: flex; 28 | align-items: center; 29 | justify-content: center; 30 | } 31 | 32 | img.icon { 33 | width: 20px; 34 | height: 20px; 35 | } 36 | 37 | .separator { 38 | border-top: 1.5px solid #44494a; 39 | } 40 | 41 | .post { 42 | padding: 10px 20px; 43 | margin-top: 20px; 44 | margin-bottom: 20px; 45 | } 46 | 47 | .postText { 48 | word-wrap: break-word; 49 | font-size: 20px; 50 | color: #141214; 51 | } 52 | 53 | .postDate { 54 | color: rgb(71, 76, 71); 55 | } 56 | 57 | .post-input { 58 | height: 100px; 59 | } 60 | 61 | .userInfoCentered { 62 | margin: 0 auto; 63 | text-align: left; 64 | display: flex; 65 | align-items: center; 66 | justify-content: center; 67 | } 68 | 69 | .userInfoBox { 70 | margin-top: 10px; 71 | margin-left: 10px; 72 | width: 270px; 73 | } 74 | 75 | .userInfoBio { 76 | word-wrap: anywhere; 77 | overflow-wrap: anywhere; 78 | margin-top: 5px; 79 | margin-left:-110px; 80 | font-style: italic; 81 | font-size: 18px; 82 | } 83 | 84 | img.userInfoLocation { 85 | height: 10px; 86 | width: 10px; 87 | } 88 | 89 | .userInfoPostsMargin { 90 | margin-left:-140px; 91 | } 92 | 93 | img.pfp { 94 | height: 100px; 95 | width: 100px; 96 | border-radius: 4%; 97 | display: block; 98 | } 99 | 100 | .userPfp { 101 | margin-bottom: 50px; 102 | } 103 | 104 | .leftSidebar { 105 | width: 15%; 106 | background-color:white; 107 | position: fixed; 108 | height: 100%; 109 | border-right: 1.5px solid #44494a; 110 | text-align: center !important; 111 | } 112 | 113 | .rightSidebar { 114 | margin: 0px; 115 | width: 15%; 116 | position: fixed; 117 | top: 0; 118 | right: 0; 119 | height: 100%; 120 | text-align: left; 121 | background-color: white; 122 | border-left: 1.5px solid #44494a; 123 | } 124 | 125 | .content { 126 | height: 1000px; 127 | word-wrap: break-word; 128 | text-align: center; 129 | padding: 10px; 130 | } 131 | 132 | @media screen and (max-width: 800px) { 133 | .left-sidebar { 134 | visibility: hidden; 135 | } 136 | .right-sidebar { 137 | visibility: hidden; 138 | } 139 | .sidebar a {float: left;} 140 | div.content {margin-left: 0;} 141 | } -------------------------------------------------------------------------------- /backend/views/post.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import get_object_or_404 2 | from django.http import JsonResponse 3 | 4 | from backend.models import User, Post, PostRate 5 | from backend.serializers import PostSerializer, PostRateSerializer 6 | from backend.permissions import IsPostOwner 7 | 8 | from rest_framework import generics, viewsets, mixins, permissions 9 | from rest_framework.decorators import action 10 | 11 | class PostViewSet(viewsets.ModelViewSet): 12 | 13 | serializer_class = PostSerializer 14 | queryset = Post.objects.all() 15 | permission_classes = [permissions.IsAuthenticated, IsPostOwner] 16 | 17 | @action(detail=False, methods=['GET'], name='Get comments') 18 | def list_comments(self, request, *args, **kwargs): 19 | queryset = Post.objects.filter(in_reply_to_post = self.kwargs["pk"]) 20 | serializer = self.get_serializer(queryset) 21 | return Response(serializer.data) 22 | 23 | def get_queryset(self): 24 | if self.action == 'list': 25 | return Post.objects.filter(in_reply_to_post = None).order_by('-pub_date') 26 | return Post.objects.order_by('-pub_date') 27 | 28 | class PostRateViewSet(generics.GenericAPIView): # use mixins instead 29 | queryset = PostRate.objects.all() 30 | serializer_class = PostRateSerializer 31 | 32 | def get(self, request, pk): 33 | post = get_object_or_404(Post, pk = pk) 34 | data = { 35 | 'likes_count': PostRate.objects.filter(liked = True, rated_post = post).count(), 36 | 'dislikes_count': PostRate.objects.filter(liked = False, rated_post = post).count() 37 | } 38 | return JsonResponse(data) 39 | 40 | def post(self, request, *args, **kwargs): 41 | post = get_object_or_404(Post, pk = request.data["rated_post"]["id"]) 42 | post_rating = PostRate.objects.filter(rated_by = request.user, rated_post = post).first() 43 | user_liked_post = request.data["liked"] 44 | 45 | if post_rating: 46 | if user_liked_post: 47 | if post_rating.liked: 48 | post_rating.liked = None 49 | else: 50 | post_rating.liked = True 51 | elif not user_liked_post: 52 | if post_rating.liked == False: 53 | post_rating.liked = None 54 | else: 55 | post_rating.liked = False 56 | else: 57 | post_rating = PostRate(liked = user_liked_post, rated_by = request.user, rated_post = post) 58 | 59 | post_rating.save() 60 | 61 | data = { 62 | 'total_likes': PostRate.objects.filter(liked = True, rated_post = post).count(), 63 | 'total_dislikes': PostRate.objects.filter(liked = False, rated_post = post).count() 64 | } 65 | return JsonResponse(data) 66 | 67 | class CommentList(generics.ListAPIView): # turn this into a method in postviewset 68 | serializer_class = PostSerializer 69 | 70 | def get_queryset(self): 71 | return Post.objects.filter(in_reply_to_post = self.kwargs["pk"]) -------------------------------------------------------------------------------- /backend/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from backend.models import Post, PostRate, Profile, Follower 3 | from django.contrib.auth.models import User 4 | 5 | class ProfileSerializer(serializers.ModelSerializer): 6 | username = serializers.CharField(source = 'get_username') 7 | user_id = serializers.IntegerField(source = 'get_user_id') 8 | followers_count = serializers.IntegerField(source = 'get_followers_count') 9 | following_count = serializers.IntegerField(source = 'get_following_count') 10 | profile_belongs_to_authenticated_user = serializers.BooleanField(source = 'get_profile_belongs_to_authenticated_user') 11 | follow_status = serializers.CharField(source = 'get_follow_status') 12 | 13 | class Meta: 14 | model = Profile 15 | fields = ('username', 'user_id', 'followers_count', 'following_count', 'profile_belongs_to_authenticated_user', 'follow_status', 'first_name', 'last_name', 'bio', 'location') 16 | read_only_fields = ('username', 'user_id', 'followers_count', 'following_count', 'profile_belongs_to_authenticated_user', 'follow_status') 17 | 18 | class FollowerSerializer(serializers.ModelSerializer): 19 | user = serializers.DictField(child = serializers.CharField(), source = 'get_user_info', read_only = True) 20 | is_followed_by = serializers.DictField(child = serializers.CharField(), source = 'get_is_followed_by_info', read_only = True) 21 | 22 | class Meta: 23 | model = Follower 24 | fields = ('user', 'is_followed_by') 25 | read_only_fields = ('user', 'is_followed_by') 26 | 27 | # Serializer for when someone signs up, currently unused 28 | class CreateUserSerializer(serializers.ModelSerializer): 29 | class Meta: 30 | model = User 31 | fields = ('id', 'username', 'password') 32 | extra_kwargs = {'password': {'write_only': True}} 33 | 34 | def create(self, validated_data): 35 | user = User.objects.create_user(validated_data['username'], None, validated_data['password']) 36 | return user 37 | 38 | class PostSerializer(serializers.ModelSerializer): 39 | post_belongs_to_authenticated_user = serializers.BooleanField(source = 'get_post_belongs_to_authenticated_user', read_only = True) 40 | posted_by = serializers.DictField(child = serializers.CharField(), source = 'get_user', read_only = True) 41 | pub_date = serializers.CharField(source = 'get_readable_date', read_only = True) 42 | 43 | likes_count = serializers.IntegerField(source='get_likes_count', read_only = True) 44 | dislikes_count = serializers.IntegerField(source='get_dislikes_count', read_only = True) 45 | comments_count = serializers.IntegerField(source='get_comments_count', read_only = True) 46 | 47 | class Meta: 48 | model = Post 49 | fields = ['id', 'post_belongs_to_authenticated_user', 'posted_by', 'pub_date', 'text', 'image', 'in_reply_to_post', 'likes_count', 'dislikes_count', 'comments_count'] 50 | write_only_fields = ['text', 'image', 'in_reply_to_post'] 51 | 52 | class PostRateSerializer(serializers.ModelSerializer): 53 | 54 | class Meta: 55 | model = PostRate 56 | fields = ['liked', 'rated_post'] -------------------------------------------------------------------------------- /frontend/src/components/Profile.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Cookies from 'js-cookie'; 4 | import classnames from 'classnames' 5 | 6 | import styles from './App.css'; 7 | import { FollowButton } from './Misc' 8 | 9 | export function Profile(props) { 10 | const profileId = parseInt(props.match.params.id); 11 | 12 | const [userInfo, setUserInfo] = useState({ 13 | "user_id": null, 14 | "username": null, 15 | "first_name": null, 16 | "last_name": null, 17 | "bio": null, 18 | "location": null, 19 | "total_followers": null, 20 | "total_followed": null, 21 | "user_info": null, 22 | }); 23 | 24 | useEffect(() => { 25 | fetch(`/api/profile/${profileId}/`, { 26 | headers: { 27 | 'Content-Type': 'application/json', 28 | 'Authorization': Cookies.get('sessionid'), 29 | }, 30 | }) 31 | .then(response => { return response.json() }) 32 | .then(data => { setUserInfo(data) }) 33 | }, []) 34 | 35 | return ( 36 |
37 |
38 |
39 | { 40 | (userInfo.profile_picture) 41 | ? { 42 | : { 43 | } 44 |
45 | 46 |
47 | 48 |
49 | { userInfo.username } 50 |
51 | 52 |
53 | { userInfo.first_name } { userInfo.last_name } 54 |
55 | 56 |
57 | 58 | { 59 | (userInfo.location) 60 | ? {userInfo.location} 61 | : The Earth 62 | } 63 |
64 | 65 |
66 | following: { userInfo.following_count } 67 |
68 | followers: { userInfo.followers_count } 69 |
70 | 71 |
72 | { 73 | (userInfo.bio) 74 | ? {userInfo.bio} 75 | : Bio mising 76 | } 77 |
78 | 79 |
80 |
81 | { 82 | (!userInfo.profile_belongs_to_authenticated_user) 83 | ? 84 | : 85 | } 86 |
87 |
88 |
89 |
90 |
91 |
92 | ) 93 | } -------------------------------------------------------------------------------- /backend/static/social/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: #1b1919; 4 | } 5 | 6 | .left-sidebar { 7 | width: 15%; 8 | background-color:white; 9 | position: fixed; 10 | height: 100%; 11 | border-right: 1.5px solid #44494a; 12 | text-align: center !important; 13 | } 14 | 15 | .right-sidebar { 16 | margin: 0px; 17 | width: 15%; 18 | position: fixed; 19 | top: 0; 20 | right: 0; 21 | height: 100%; 22 | text-align: left; 23 | background-color: white; 24 | border-left: 1.5px solid #44494a; 25 | } 26 | 27 | .content { 28 | height: 1000px; 29 | word-wrap: break-word; 30 | text-align: center; 31 | padding: 10px; 32 | } 33 | 34 | @media screen and (max-width: 800px) { 35 | .left-sidebar { 36 | visibility: hidden; 37 | } 38 | .right-sidebar { 39 | visibility: hidden; 40 | } 41 | .sidebar a {float: left;} 42 | div.content {margin-left: 0;} 43 | } 44 | 45 | .center{ 46 | margin: 0 auto; 47 | text-align: center; 48 | } 49 | 50 | .homecenter { 51 | margin: 0 auto; 52 | display: flex; 53 | align-items: center; 54 | justify-content: center; 55 | } 56 | 57 | .userinfo-centered { 58 | margin: 0 auto; 59 | text-align: left; 60 | display: flex; 61 | align-items: center; 62 | justify-content: center; 63 | } 64 | 65 | .userinfo-box { 66 | margin-left: 10px; 67 | width: 270px; 68 | } 69 | 70 | .userinfo-bio { 71 | margin-top: 5px; 72 | margin-left:-110px; 73 | font-style: italic; 74 | font-size: 18px; 75 | } 76 | 77 | img.userinfo-location { 78 | height: 10px; 79 | width: 10px; 80 | } 81 | 82 | .userinfo-posts-margin { 83 | margin-left:-140px; 84 | } 85 | 86 | ul { 87 | list-style-type: none; 88 | } 89 | 90 | .comment-separator { 91 | border-top: 1.5px solid #44494a; 92 | } 93 | 94 | li.post { 95 | padding: 10px 20px; 96 | margin-top: 20px; 97 | margin-bottom: 20px; 98 | border-top: 1.5px solid #44494a; 99 | } 100 | 101 | li.comment { 102 | margin-top: 15px; 103 | border-left: #0069d9 2px solid; 104 | } 105 | 106 | li.inreply { 107 | margin-top: 15px; 108 | margin-left: 30px; 109 | border-left: #1a1ad9 2px solid; 110 | } 111 | 112 | .comment-text { 113 | color: #141214; 114 | text-align: left; 115 | } 116 | 117 | .comment-body { 118 | margin-left: 15px; 119 | } 120 | 121 | img.icon { 122 | width: 20px; 123 | height: 20px; 124 | } 125 | 126 | img.pfp { 127 | height: 100px; 128 | width: 100px; 129 | border-radius: 4%; 130 | display: block; 131 | } 132 | 133 | .user-pfp { 134 | margin-bottom: 130px; 135 | } 136 | 137 | .hidden { 138 | display: none; 139 | } 140 | 141 | .center { 142 | text-align: center; 143 | display: block; 144 | } 145 | 146 | .post-text { 147 | word-wrap: break-word; 148 | font-size: 20px; 149 | color: #141214; 150 | text-align: left; 151 | } 152 | 153 | .post-header { 154 | text-align: left; 155 | } 156 | 157 | .post-date { 158 | color: rgb(71, 76, 71); 159 | } 160 | 161 | .post-username { 162 | text-align: left; 163 | } 164 | 165 | .post-input { 166 | height: 100px; 167 | } 168 | 169 | textarea { 170 | resize: none !important; 171 | padding: 10px 25px; 172 | border: 1.5px solid #ccc; 173 | border-radius: 4px; 174 | } -------------------------------------------------------------------------------- /frontend/static/social/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: white; 3 | color: #1b1919; 4 | } 5 | 6 | .left-sidebar { 7 | width: 15%; 8 | background-color:white; 9 | position: fixed; 10 | height: 100%; 11 | border-right: 1.5px solid #44494a; 12 | text-align: center !important; 13 | } 14 | 15 | .right-sidebar { 16 | margin: 0px; 17 | width: 15%; 18 | position: fixed; 19 | top: 0; 20 | right: 0; 21 | height: 100%; 22 | text-align: left; 23 | background-color: white; 24 | border-left: 1.5px solid #44494a; 25 | } 26 | 27 | .content { 28 | height: 1000px; 29 | word-wrap: break-word; 30 | text-align: center; 31 | padding: 10px; 32 | } 33 | 34 | @media screen and (max-width: 800px) { 35 | .left-sidebar { 36 | visibility: hidden; 37 | } 38 | .right-sidebar { 39 | visibility: hidden; 40 | } 41 | .sidebar a {float: left;} 42 | div.content {margin-left: 0;} 43 | } 44 | 45 | .userinfo-centered { 46 | margin: 0 auto; 47 | text-align: left; 48 | display: flex; 49 | align-items: center; 50 | justify-content: center; 51 | } 52 | 53 | .userinfo-box { 54 | margin-left: 10px; 55 | width: 270px; 56 | } 57 | 58 | .userinfo-bio { 59 | margin-top: 5px; 60 | margin-left:-110px; 61 | font-style: italic; 62 | font-size: 18px; 63 | } 64 | 65 | img.userinfo-location { 66 | height: 10px; 67 | width: 10px; 68 | } 69 | 70 | .userinfo-posts-margin { 71 | margin-left:-140px; 72 | } 73 | 74 | ul { 75 | list-style-type: none; 76 | } 77 | 78 | li.post { 79 | width: 64.35%; 80 | margin-left: 16.35%; 81 | padding: 10px 20px; 82 | margin-top: 20px; 83 | margin-bottom: 20px; 84 | } 85 | 86 | li.comment { 87 | margin-top: 15px; 88 | border-left: #0069d9 2px solid; 89 | } 90 | 91 | li.inreply { 92 | margin-top: 15px; 93 | margin-left: 30px; 94 | border-left: #1a1ad9 2px solid; 95 | } 96 | 97 | .comment-text { 98 | color: #141214; 99 | text-align: left; 100 | } 101 | 102 | .comment-body { 103 | margin-left: 15px; 104 | } 105 | 106 | img.icon { 107 | width: 20px; 108 | height: 20px; 109 | } 110 | 111 | img.pfp { 112 | height: 100px; 113 | width: 100px; 114 | border-radius: 4%; 115 | display: block; 116 | } 117 | 118 | .user-pfp { 119 | margin-bottom: 130px; 120 | } 121 | 122 | .hidden { 123 | display: none; 124 | } 125 | 126 | .center { 127 | text-align: center; 128 | display: block; 129 | } 130 | 131 | .center{ 132 | margin: 0 auto; 133 | text-align: center; 134 | } 135 | 136 | .homecenter { 137 | margin: 0 auto; 138 | display: flex; 139 | align-items: center; 140 | justify-content: center; 141 | } 142 | 143 | .index-posts { 144 | position: absolute; 145 | display: flex; 146 | align-items: center; 147 | justify-content: center; 148 | } 149 | 150 | .comment-separator { 151 | border-top: 1.5px solid #44494a; 152 | } 153 | 154 | .post { 155 | width: 64.35%; 156 | padding: 10px 20px; 157 | margin-top: 20px; 158 | margin-bottom: 20px; 159 | } 160 | 161 | .post-text { 162 | word-wrap: break-word; 163 | font-size: 20px; 164 | color: #141214; 165 | text-align: left; 166 | } 167 | 168 | .post-date { 169 | color: rgb(71, 76, 71); 170 | } 171 | 172 | .post-username { 173 | text-align: left; 174 | } 175 | 176 | .post-input { 177 | height: 100px; 178 | } 179 | 180 | textarea { 181 | resize: none !important; 182 | padding: 10px 25px; 183 | border: 1.5px solid #ccc; 184 | border-radius: 4px; 185 | } -------------------------------------------------------------------------------- /backend/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2 on 2020-08-05 10:52 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Profile', 19 | fields=[ 20 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('bio', models.CharField(blank=True, max_length=100)), 22 | ('location', models.CharField(blank=True, max_length=100)), 23 | ('profile_picture', models.ImageField(blank=True, upload_to='profile-pictures')), 24 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='Post', 29 | fields=[ 30 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('pub_date', models.DateTimeField(auto_now=True, verbose_name='Publication Date')), 32 | ('post_text', models.CharField(max_length=200)), 33 | ('image', models.ImageField(blank=True, upload_to='post-images')), 34 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 35 | ], 36 | ), 37 | migrations.CreateModel( 38 | name='Like', 39 | fields=[ 40 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 41 | ('liked_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 42 | ('liked_post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.Post')), 43 | ], 44 | ), 45 | migrations.CreateModel( 46 | name='Follower', 47 | fields=[ 48 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 49 | ('is_followed_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='is_followed_by', to=settings.AUTH_USER_MODEL)), 50 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), 51 | ], 52 | ), 53 | migrations.CreateModel( 54 | name='Dislike', 55 | fields=[ 56 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 57 | ('disliked_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 58 | ('disliked_post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.Post')), 59 | ], 60 | ), 61 | migrations.CreateModel( 62 | name='Comment', 63 | fields=[ 64 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 65 | ('comment', models.CharField(max_length=200)), 66 | ('post_date', models.DateTimeField(verbose_name='Publication Date')), 67 | ('in_reply_to_comment', models.IntegerField(blank=True, null=True)), 68 | ('in_reply_to_user', models.IntegerField(blank=True, null=True)), 69 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='backend.Post')), 70 | ('posted_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 71 | ], 72 | ), 73 | ] 74 | -------------------------------------------------------------------------------- /backend/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | from django_currentuser.db.models import CurrentUserField 4 | from django_currentuser.middleware import get_current_authenticated_user 5 | 6 | 7 | # A primitive extension of the standard User table from Django lib 8 | class Profile(models.Model): 9 | user = models.OneToOneField(User, on_delete=models.CASCADE) 10 | first_name = models.CharField(max_length=100, null=True, blank=True) 11 | last_name = models.CharField(max_length=100, null=True, blank=True) 12 | bio = models.CharField(max_length=100, null=True, blank=True) 13 | location = models.CharField(max_length=100, null=True, blank=True) 14 | profile_picture = models.ImageField(upload_to="profile-pictures", null=True, blank=True) 15 | 16 | def get_user_id(self): 17 | return self.user.pk 18 | 19 | def get_username(self): 20 | return self.user.username 21 | 22 | def get_followers_count(self): 23 | return Follower.objects.filter(user = self.user).exclude(is_followed_by = self.user).count() 24 | 25 | def get_following_count(self): 26 | return Follower.objects.filter(is_followed_by = self.user).count() 27 | 28 | def get_follow_status(self): 29 | follow_status = Follower.objects.filter(user = self.user, is_followed_by = get_current_authenticated_user()) 30 | return "Following" if follow_status else "Follow" 31 | 32 | def get_profile_belongs_to_authenticated_user(self): 33 | return self.user == get_current_authenticated_user() 34 | 35 | def __str__(self): 36 | return str(self.user) 37 | 38 | 39 | class Post(models.Model): 40 | text = models.CharField(max_length=200) 41 | posted_by = CurrentUserField(related_name='posted_by') 42 | pub_date = models.DateTimeField('Publication Date', auto_now=True) 43 | image = models.ImageField(upload_to='post-images', null=True) 44 | in_reply_to_post = models.IntegerField(null=True) 45 | 46 | def get_readable_date(self): 47 | return self.pub_date.strftime("%B %d, %Y") 48 | 49 | def get_post_belongs_to_authenticated_user(self): 50 | return self.posted_by.pk == get_current_authenticated_user().pk 51 | 52 | def get_user(self): 53 | user_dict = vars(self.posted_by) 54 | return {"id": user_dict["id"], "username": user_dict["username"]} 55 | 56 | def get_likes_count(self): 57 | return PostRate.objects.filter(liked=True, rated_post=self).count() 58 | 59 | def get_dislikes_count(self): 60 | return PostRate.objects.filter(liked=False, rated_post=self).count() 61 | 62 | def get_comments(self): 63 | return Post.objects.filter(in_reply_to_post=self.pk) 64 | 65 | def get_comments_count(self): 66 | return Post.objects.filter(in_reply_to_post=self.pk).count() 67 | 68 | def __str__(self): 69 | return str(self) 70 | 71 | 72 | class PostRate(models.Model): 73 | liked = models.BooleanField(null=True) 74 | rated_post = models.ForeignKey(Post, on_delete=models.CASCADE) 75 | rated_by = models.ForeignKey(User, on_delete=models.CASCADE) 76 | 77 | def __str__(self): 78 | return str(self.rated_post) 79 | 80 | 81 | class Follower(models.Model): #rename model to UserFollows or find a better name 82 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user') 83 | is_followed_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='is_followed_by') 84 | 85 | def get_user_info(self): 86 | user_dict = vars(self.user) 87 | return {"id": user_dict["id"], "username": user_dict["username"]} 88 | 89 | def get_is_followed_by_info(self): 90 | user_dict = vars(self.is_followed_by) 91 | return {"id": user_dict["id"], "username": user_dict["username"]} 92 | 93 | def get_following(self, user): 94 | return Follower.objects.filter(is_followed_by=user) 95 | 96 | def get_followers(self, user): 97 | return Follower.objects.filter(user=user).exclude(is_followed_by=user) 98 | 99 | def get_following_count(self, user): 100 | return Follower.objects.filter(is_followed_by=user).count() 101 | 102 | def get_followers_count(self, user): 103 | return Follower.objects.filter(user=user).count() 104 | 105 | def __str__(self): 106 | return str(self) 107 | -------------------------------------------------------------------------------- /socialproject/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for root project. 3 | 4 | Generated by 'django-admin startproject' using Django 2.0.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.0/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/2.0/ref/settings/ 11 | """ 12 | 13 | import os 14 | 15 | # Build paths inside the project like this: os.path.join(BASE_DIR, ...) 16 | BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = '7o8#*@3%krufub)s(_ks9qr^6&o9e(j(_)=id92)!o#i2w315e' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | LOGIN_REDIRECT_URL = "/" 31 | LOGOUT_REDIRECT_URL = '/' 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | 'django.contrib.admin', 36 | 'django.contrib.auth', 37 | 'django.contrib.contenttypes', 38 | 'django.contrib.sessions', 39 | 'django.contrib.messages', 40 | 'django.contrib.staticfiles', 41 | 'backend.apps.BackendConfig', 42 | 'frontend', 43 | 'rest_framework', 44 | 'rest_framework.authtoken', 45 | ] 46 | 47 | MIDDLEWARE = [ 48 | 'django.middleware.security.SecurityMiddleware', 49 | 'django.contrib.sessions.middleware.SessionMiddleware', 50 | 'corsheaders.middleware.CorsMiddleware', 51 | 'django.middleware.common.CommonMiddleware', 52 | 'django.middleware.csrf.CsrfViewMiddleware', 53 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 54 | 'django.contrib.messages.middleware.MessageMiddleware', 55 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 56 | 'django_currentuser.middleware.ThreadLocalUserMiddleware', 57 | ] 58 | 59 | REST_FRAMEWORK = { 60 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 61 | 'rest_framework.authentication.SessionAuthentication', 62 | 'rest_framework.authentication.TokenAuthentication', 63 | ), 64 | 'DEFAULT_PERMISSION_CLASSES': ( 65 | 'rest_framework.permissions.IsAuthenticated', ) 66 | } 67 | 68 | ROOT_URLCONF = 'socialproject.urls' 69 | 70 | AUTHENTICATION_BACKENDS = ( 71 | 'django.contrib.auth.backends.ModelBackend', 72 | ) 73 | 74 | TEMPLATES = [ 75 | { 76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 77 | 'DIRS': [], 78 | 'APP_DIRS': True, 79 | 'OPTIONS': { 80 | 'context_processors': [ 81 | 'django.template.context_processors.debug', 82 | 'django.template.context_processors.request', 83 | 'django.contrib.auth.context_processors.auth', 84 | 'django.contrib.messages.context_processors.messages', 85 | ], 86 | }, 87 | }, 88 | ] 89 | EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' 90 | WSGI_APPLICATION = 'socialproject.wsgi.application' 91 | 92 | 93 | # Database 94 | # https://docs.djangoproject.com/en/2.0/ref/settings/#databases 95 | 96 | DATABASES = { 97 | 'default': { 98 | 'ENGINE': 'django.db.backends.sqlite3', 99 | 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 100 | } 101 | } 102 | 103 | 104 | # Password validation 105 | # https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators 106 | 107 | AUTH_PASSWORD_VALIDATORS = [ 108 | { 109 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 110 | }, 111 | { 112 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 113 | }, 114 | { 115 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 116 | }, 117 | { 118 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 119 | }, 120 | ] 121 | 122 | 123 | # Internationalization 124 | # https://docs.djangoproject.com/en/2.0/topics/i18n/ 125 | 126 | LANGUAGE_CODE = 'en-us' 127 | 128 | TIME_ZONE = 'Europe/Kiev' 129 | 130 | USE_I18N = True 131 | 132 | USE_L10N = True 133 | 134 | USE_TZ = True 135 | 136 | 137 | # Static files (CSS, JavaScript, Images) 138 | # https://docs.djangoproject.com/en/2.0/howto/static-files/ 139 | 140 | STATIC_URL = '/static/' 141 | 142 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 143 | MEDIA_URL = '/media/' 144 | -------------------------------------------------------------------------------- /frontend/src/components/Settings.js: -------------------------------------------------------------------------------- 1 | import React, { Component, useState } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Cookies from 'js-cookie'; 5 | import classnames from 'classnames' 6 | import styles from './App.css'; 7 | 8 | export function Settings(props) { 9 | return ( 10 |
11 |
12 |
13 | ) 14 | } 15 | function Form() { 16 | const [state, setState] = useState({ 17 | first_name: "", 18 | last_name: "", 19 | bio: "", 20 | location: "", 21 | }) 22 | function handleChange(event) { 23 | const value = event.target.value; 24 | setState({ 25 | ...state, 26 | [event.target.name]: value 27 | }); 28 | }; 29 | 30 | function handleSubmit(event) { 31 | event.preventDefault(); 32 | fetch(`/api/profile/`, { 33 | headers: { 34 | 'Content-Type': 'application/json', 35 | 'Authorization': Cookies.get('sessionid'), 36 | 'X-CSRFToken': Cookies.get('csrftoken') 37 | }, 38 | method: 'PATCH', 39 | body: JSON.stringify(state) 40 | }) 41 | .then(response => { return response.json() }) 42 | .then(data => setState({ fetchData: data })) 43 | } 44 | 45 | return ( 46 |
47 | 48 |