An Hang commited on
Commit
1314536
·
unverified ·
1 Parent(s): 7d43306

add like (#40)

Browse files

add like and follow

src/social/admin.py CHANGED
@@ -1,5 +1,5 @@
1
  from django.contrib import admin
2
- from .models import Post #, Comment
3
 
4
  admin.site.register(Post)
5
- # admin.site.register(Comment)
 
1
  from django.contrib import admin
2
+ from .models import Post, UserProfile
3
 
4
  admin.site.register(Post)
5
+ admin.site.register(UserProfile)
src/social/forms.py CHANGED
@@ -1,14 +1,13 @@
1
  from django import forms
2
  from .models import Post, Comment
3
 
4
-
5
  class PostForm(forms.ModelForm):
6
  body = forms.CharField(
7
  label='',
8
- widget=forms.Textarea(
9
- attrs={'rows': '3',
10
- 'placeholder': 'Say Something...'}
11
- ))
12
 
13
  class Meta:
14
  model = Post
@@ -17,10 +16,10 @@ class PostForm(forms.ModelForm):
17
  class CommentForm(forms.ModelForm):
18
  comment = forms.CharField(
19
  label='',
20
- widget=forms.Textarea(
21
- attrs={'rows': '3',
22
- 'placeholder': 'Say Something...'}
23
- ))
24
 
25
  class Meta:
26
  model = Comment
 
1
  from django import forms
2
  from .models import Post, Comment
3
 
 
4
  class PostForm(forms.ModelForm):
5
  body = forms.CharField(
6
  label='',
7
+ widget=forms.Textarea(attrs={
8
+ 'rows': '3',
9
+ 'placeholder': 'Say Something...'
10
+ }))
11
 
12
  class Meta:
13
  model = Post
 
16
  class CommentForm(forms.ModelForm):
17
  comment = forms.CharField(
18
  label='',
19
+ widget=forms.Textarea(attrs={
20
+ 'rows': '3',
21
+ 'placeholder': 'Say Something...'
22
+ }))
23
 
24
  class Meta:
25
  model = Comment
src/social/migrations/0001_initial.py CHANGED
@@ -1,4 +1,4 @@
1
- # Generated by Django 3.1.4 on 2020-12-20 00:03
2
 
3
  from django.conf import settings
4
  from django.db import migrations, models
@@ -24,14 +24,4 @@ class Migration(migrations.Migration):
24
  ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
25
  ],
26
  ),
27
- migrations.CreateModel(
28
- name='Comment',
29
- fields=[
30
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
31
- ('comment', models.TextField()),
32
- ('created_on', models.DateTimeField(default=django.utils.timezone.now)),
33
- ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
34
- ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='social.post')),
35
- ],
36
- ),
37
  ]
 
1
+ # Generated by Django 2.1.4 on 2020-12-21 23:54
2
 
3
  from django.conf import settings
4
  from django.db import migrations, models
 
24
  ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
25
  ],
26
  ),
 
 
 
 
 
 
 
 
 
 
27
  ]
src/social/migrations/0002_comment.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 3.1.4 on 2021-01-01 18:58
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+ import django.db.models.deletion
6
+ import django.utils.timezone
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
13
+ ('social', '0001_initial'),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name='Comment',
19
+ fields=[
20
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21
+ ('comment', models.TextField()),
22
+ ('created_on', models.DateTimeField(default=django.utils.timezone.now)),
23
+ ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
24
+ ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='social.post')),
25
+ ],
26
+ ),
27
+ ]
src/social/migrations/0003_userprofile.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 3.1.4 on 2021-01-17 18:24
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ ('auth', '0012_alter_user_first_name_max_length'),
11
+ ('social', '0002_comment'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.CreateModel(
16
+ name='UserProfile',
17
+ fields=[
18
+ ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, related_name='profile', serialize=False, to='auth.user', verbose_name='user')),
19
+ ('name', models.CharField(blank=True, max_length=30, null=True)),
20
+ ('bio', models.TextField(blank=True, max_length=500, null=True)),
21
+ ('birth_date', models.DateField(blank=True, null=True)),
22
+ ('location', models.CharField(blank=True, max_length=100, null=True)),
23
+ ('picture', models.ImageField(blank=True, default='uploads/profile_pictures/default.png', upload_to='uploads/profile_pictures')),
24
+ ],
25
+ ),
26
+ ]
src/social/migrations/0004_userprofile_followers.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 3.1.4 on 2021-01-26 00:21
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11
+ ('social', '0003_userprofile'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='userprofile',
17
+ name='followers',
18
+ field=models.ManyToManyField(blank=True, related_name='followers', to=settings.AUTH_USER_MODEL),
19
+ ),
20
+ ]
src/social/migrations/0005_auto_20210131_1615.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 3.1.4 on 2021-01-31 16:15
2
+
3
+ from django.conf import settings
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+
9
+ dependencies = [
10
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11
+ ('social', '0004_userprofile_followers'),
12
+ ]
13
+
14
+ operations = [
15
+ migrations.AddField(
16
+ model_name='post',
17
+ name='dislikes',
18
+ field=models.ManyToManyField(blank=True, related_name='dislikes', to=settings.AUTH_USER_MODEL),
19
+ ),
20
+ migrations.AddField(
21
+ model_name='post',
22
+ name='likes',
23
+ field=models.ManyToManyField(blank=True, related_name='likes', to=settings.AUTH_USER_MODEL),
24
+ ),
25
+ ]
src/social/migrations/0006_alter_comment_id_alter_post_id.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated by Django 5.0.3 on 2024-04-01 10:12
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('social', '0005_auto_20210131_1615'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterField(
14
+ model_name='comment',
15
+ name='id',
16
+ field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
17
+ ),
18
+ migrations.AlterField(
19
+ model_name='post',
20
+ name='id',
21
+ field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
22
+ ),
23
+ ]
src/social/models.py CHANGED
@@ -1,16 +1,37 @@
1
  from django.db import models
2
  from django.utils import timezone
3
  from django.contrib.auth.models import User
 
 
4
 
5
 
6
  class Post(models.Model):
7
  body = models.TextField()
8
  created_on = models.DateTimeField(default=timezone.now)
9
  author = models.ForeignKey(User, on_delete=models.CASCADE)
10
-
 
11
 
12
  class Comment(models.Model):
13
- comment = models.TextField()
14
- created_on = models.DateTimeField(default=timezone.now)
15
- post = models.ForeignKey('Post', on_delete=models.CASCADE)
16
- author = models.ForeignKey(User, on_delete=models.CASCADE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from django.db import models
2
  from django.utils import timezone
3
  from django.contrib.auth.models import User
4
+ from django.db.models.signals import post_save
5
+ from django.dispatch import receiver
6
 
7
 
8
  class Post(models.Model):
9
  body = models.TextField()
10
  created_on = models.DateTimeField(default=timezone.now)
11
  author = models.ForeignKey(User, on_delete=models.CASCADE)
12
+ likes = models.ManyToManyField(User, blank=True, related_name='likes')
13
+ dislikes = models.ManyToManyField(User, blank=True, related_name='dislikes')
14
 
15
  class Comment(models.Model):
16
+ comment = models.TextField()
17
+ created_on = models.DateTimeField(default=timezone.now)
18
+ author = models.ForeignKey(User, on_delete=models.CASCADE)
19
+ post = models.ForeignKey('Post', on_delete=models.CASCADE)
20
+
21
+ class UserProfile(models.Model):
22
+ user = models.OneToOneField(User, primary_key=True, verbose_name='user', related_name='profile', on_delete=models.CASCADE)
23
+ name = models.CharField(max_length=30, blank=True, null=True)
24
+ bio = models.TextField(max_length=500, blank=True, null=True)
25
+ birth_date=models.DateField(null=True, blank=True)
26
+ location = models.CharField(max_length=100, blank=True, null=True)
27
+ picture = models.ImageField(upload_to='uploads/profile_pictures', default='uploads/profile_pictures/default.png', blank=True)
28
+ followers = models.ManyToManyField(User, blank=True, related_name='followers')
29
+
30
+ @receiver(post_save, sender=User)
31
+ def create_user_profile(sender, instance, created, **kwargs):
32
+ if created:
33
+ UserProfile.objects.create(user=instance)
34
+
35
+ @receiver(post_save, sender=User)
36
+ def save_user_profile(sender, instance, **kwargs):
37
+ instance.profile.save()
src/social/templates/social/comment_delete.html CHANGED
@@ -1,24 +1,24 @@
1
  {% extends 'landing/base.html' %}
2
- {% load crispy_forms_tags %}
3
 
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
- <div class="col-md-3 col-sm-6">
8
- <a href="{% url 'post-detail' object.post.pk %}" class="btn btn-light">Back to Feed</a>
9
  </div>
10
  </div>
11
- <div class="row justify-content-center mt-5">
12
- <div class="col-md-5 col-sm-12">
13
- <h5>Are You Sure?</h5>
14
- <p>You are about to delete this comment, this cannot be undone.</p>
15
- <form method="POST">
16
- {% csrf_token %}
17
- <div class="d-grid gap-2">
18
- <button type="submit" class="btn btn-danger">Delete</button>
19
- </div>
20
- </form>
21
- </div>
 
22
  </div>
23
  </div>
24
  {% endblock content %}
 
1
  {% extends 'landing/base.html' %}
 
2
 
3
  {% block content %}
4
  <div class="container">
5
  <div class="row mt-5">
6
+ <div class="col-md-5 col-sm-6">
7
+ <a href="{% url 'post-detail' object.pk %}" class="btn btn-light">Back To Post</a>
8
  </div>
9
  </div>
10
+
11
+ <div class="row justify-content-center mt-3 mb-5">
12
+ <div class="col-md-5 col-sm-12 border-bottom">
13
+ <form method="POST">
14
+ {% csrf_token %}
15
+ <h5>Are You Sure?</h5>
16
+ <p>You are about to delete this comment, this cannot be undone.</p>
17
+ <div class="d-grid gap-2">
18
+ <button class="btn btn-danger mt-3">Delete</button>
19
+ </div>
20
+ </form>
21
+ </div>
22
  </div>
23
  </div>
24
  {% endblock content %}
src/social/templates/social/post_delete.html CHANGED
@@ -1,24 +1,24 @@
1
  {% extends 'landing/base.html' %}
2
- {% load crispy_forms_tags %}
3
 
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
- <div class="col-md-3 col-sm-6">
8
- <a href="{% url 'post-detail' object.pk %}" class="btn btn-light">Back to Feed</a>
9
  </div>
10
  </div>
11
- <div class="row justify-content-center mt-5">
12
- <div class="col-md-5 col-sm-12">
13
- <h5>Are You Sure?</h5>
14
- <p>You are about to delete this post, this cannot be undone.</p>
15
- <form method="POST">
16
- {% csrf_token %}
17
- <div class="d-grid gap-2">
18
- <button type="submit" class="btn btn-danger">Delete</button>
19
- </div>
20
- </form>
21
- </div>
 
22
  </div>
23
  </div>
24
  {% endblock content %}
 
1
  {% extends 'landing/base.html' %}
 
2
 
3
  {% block content %}
4
  <div class="container">
5
  <div class="row mt-5">
6
+ <div class="col-md-5 col-sm-6">
7
+ <a href="{% url 'post-detail' object.pk %}" class="btn btn-light">Back To Post</a>
8
  </div>
9
  </div>
10
+
11
+ <div class="row justify-content-center mt-3 mb-5">
12
+ <div class="col-md-5 col-sm-12 border-bottom">
13
+ <form method="POST">
14
+ {% csrf_token %}
15
+ <h5>Are You Sure?</h5>
16
+ <p>You are about to delete this post, this cannot be undone.</p>
17
+ <div class="d-grid gap-2">
18
+ <button class="btn btn-danger mt-3">Delete</button>
19
+ </div>
20
+ </form>
21
+ </div>
22
  </div>
23
  </div>
24
  {% endblock content %}
src/social/templates/social/post_detail.html CHANGED
@@ -4,28 +4,42 @@
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
- <div class="col-md-3 col-sm-6">
8
- <a href="{% url 'post-list' %}" class="btn btn-light">Back to Feed</a>
9
  </div>
10
  </div>
 
11
  <div class="row justify-content-center mt-3">
12
  <div class="col-md-5 col-sm-12 border-bottom">
13
  <p>
14
  <strong>{{ post.author }}</strong> {{ post.created_on }}
15
  {% if request.user == post.author %}
16
- <a href="{% url 'post-edit' post.pk %}" style="color: #333;"><i class="far fa-edit"></i></a>
17
- <a href="{% url 'post-delete' post.pk %}" style="color: #333;"><i class="fas fa-trash"></i></a>
18
  {% endif %}
19
  </p>
20
  <p>{{ post.body }}</p>
21
- </div>
22
- </div>
23
 
24
- <div class="row justify-content-center mt-3">
25
- <div class="col-md-5 col-sm-12">
26
- <h5>Add a Comment!</h5>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  </div>
28
  </div>
 
29
  <div class="row justify-content-center mt-3 mb-5">
30
  <div class="col-md-5 col-sm-12">
31
  <form method="POST">
@@ -38,12 +52,12 @@
38
  </div>
39
  </div>
40
  {% for comment in comments %}
41
- <div class="row justify-content-center mt-3 mb-5 border-bottom">
42
- <div class="col-md-5 col-sm-12">
43
  <p>
44
  <strong>{{ comment.author }}</strong> {{ comment.created_on }}
45
  {% if request.user == comment.author %}
46
- <a href="{% url 'comment-delete' post.pk comment.pk %}" style="color: #333;"><i class="fas fa-trash"></i></a>
47
  {% endif %}
48
  </p>
49
  <p>{{ comment.comment }}</p>
@@ -51,4 +65,4 @@
51
  </div>
52
  {% endfor %}
53
  </div>
54
- {% endblock content %}
 
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
+ <div class="col-md-5 col-sm-6">
8
+ <a href="{% url 'post-list' %}" class="btn btn-light">Back To Feed</a>
9
  </div>
10
  </div>
11
+
12
  <div class="row justify-content-center mt-3">
13
  <div class="col-md-5 col-sm-12 border-bottom">
14
  <p>
15
  <strong>{{ post.author }}</strong> {{ post.created_on }}
16
  {% if request.user == post.author %}
17
+ <a href="{% url 'post-edit' post.pk %}" style="color: #333;"><i class="far fa-edit"></i></a>
18
+ <a href="{% url 'post-delete' post.pk %}" style="color: #333;"><i class="fas fa-trash"></i></a>
19
  {% endif %}
20
  </p>
21
  <p>{{ post.body }}</p>
 
 
22
 
23
+ <div class="d-flex flex-row">
24
+ <form method="POST" action="{% url 'like' post.pk %}">
25
+ {% csrf_token %}
26
+ <input type="hidden" name="next" value="{{ request.path }}">
27
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
28
+ <i class="far fa-thumbs-up"> <span>{{ post.likes.all.count }}</span></i>
29
+ </button>
30
+ </form>
31
+
32
+ <form method="POST" action="{% url 'dislike' post.pk %}">
33
+ {% csrf_token %}
34
+ <input type="hidden" name="next" value="{{ request.path }}">
35
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
36
+ <i class="far fa-thumbs-down"> <span>{{ post.dislikes.all.count }}</span></i>
37
+ </button>
38
+ </form>
39
+ </div>
40
  </div>
41
  </div>
42
+
43
  <div class="row justify-content-center mt-3 mb-5">
44
  <div class="col-md-5 col-sm-12">
45
  <form method="POST">
 
52
  </div>
53
  </div>
54
  {% for comment in comments %}
55
+ <div class="row justify-content-center mt-3 mb-5">
56
+ <div class="col-md-5 col-sm-12 border-bottom">
57
  <p>
58
  <strong>{{ comment.author }}</strong> {{ comment.created_on }}
59
  {% if request.user == comment.author %}
60
+ <a href="{% url 'comment-delete' post.pk comment.pk %}" style="color: #333;"><i class="fas fa-trash"></i></a>
61
  {% endif %}
62
  </p>
63
  <p>{{ comment.comment }}</p>
 
65
  </div>
66
  {% endfor %}
67
  </div>
68
+ {% endblock content %}
src/social/templates/social/post_edit.html CHANGED
@@ -4,17 +4,19 @@
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
- <div class="col-md-3 col-sm-6">
8
- <a href="{% url 'post-detail' object.pk %}" class="btn btn-light">Back to Feed</a>
9
  </div>
10
  </div>
11
- <div class="row justify-content-center mt-5">
12
- <div class="col-md-5 col-sm-12">
13
- <h5>Update Your Post</h5>
 
14
  </div>
15
  </div>
 
16
  <div class="row justify-content-center mt-3 mb-5">
17
- <div class="col-md-5 col-sm-12">
18
  <form method="POST">
19
  {% csrf_token %}
20
  {{ form | crispy }}
@@ -25,4 +27,4 @@
25
  </div>
26
  </div>
27
  </div>
28
- {% endblock content %}
 
4
  {% block content %}
5
  <div class="container">
6
  <div class="row mt-5">
7
+ <div class="col-md-5 col-sm-6">
8
+ <a href="{% url 'post-detail' object.pk %}" class="btn btn-light">Back To Post</a>
9
  </div>
10
  </div>
11
+
12
+ <div class="row justify-content-center mt-3">
13
+ <div class="col-md-5 col-sm-12 border-bottom">
14
+ <h5>Update Your Post!</h5>
15
  </div>
16
  </div>
17
+
18
  <div class="row justify-content-center mt-3 mb-5">
19
+ <div class="col-md-5 col-sm-12 border-bottom">
20
  <form method="POST">
21
  {% csrf_token %}
22
  {{ form | crispy }}
 
27
  </div>
28
  </div>
29
  </div>
30
+ {% endblock content %}
src/social/templates/social/post_list.html CHANGED
@@ -2,31 +2,52 @@
2
  {% load crispy_forms_tags %}
3
 
4
  {% block content %}
5
- <div class="container">
6
- <div class="row justify-content-center mt-3">
7
- <div class="col-md-5 col-sm-12">
8
- <h5>Add a Post!</h5>
9
- </div>
10
  </div>
11
- <div class="row justify-content-center mt-3 mb-5">
12
- <div class="col-md-5 col-sm-12">
13
- <form method="POST">
14
- {% csrf_token %}
15
- {{ form | crispy }}
16
- <div class="d-grid gap-2">
17
- <button class="btn btn-success mt-3">Submit!</button>
18
- </div>
19
- </form>
20
- </div>
 
21
  </div>
22
- {% for post in post_list %}
23
- <div class="row justify-content-center mt-3">
24
- <div class="col-md-5 col-sm-12 border-bottom position-relative">
25
- <p><strong>{{ post.author }}</strong> {{ post.created_on }}</p>
 
 
 
26
  <p>{{ post.body }}</p>
27
- <a class="stretched-link" href="{% url 'post-detail' post.pk %}"></a>
28
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  </div>
30
- {% endfor %}
31
  </div>
32
- {% endblock content %}
 
 
 
2
  {% load crispy_forms_tags %}
3
 
4
  {% block content %}
5
+ <div class="container">
6
+ <div class="row justify-content-center mt-3">
7
+ <div class="col-md-5 col-sm-12 border-bottom">
8
+ <h5>Add a Post!</h5>
 
9
  </div>
10
+ </div>
11
+
12
+ <div class="row justify-content-center mt-3 mb-5">
13
+ <div class="col-md-5 col-sm-12 border-bottom">
14
+ <form method="POST">
15
+ {% csrf_token %}
16
+ {{ form | crispy }}
17
+ <div class="d-grid gap-2">
18
+ <button class="btn btn-success mt-3">Submit!</button>
19
+ </div>
20
+ </form>
21
  </div>
22
+ </div>
23
+
24
+ {% for post in post_list %}
25
+ <div class="row justify-content-center mt-3">
26
+ <div class="col-md-5 col-sm-12 border-bottom position-relative">
27
+ <p><a style="text-decoration: none" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
28
+ <div class="position-relative">
29
  <p>{{ post.body }}</p>
30
+ <a href="{% url 'post-detail' post.pk %}" class="stretched-link"></a>
31
  </div>
32
+
33
+ <div class="d-flex flex-row">
34
+ <form method="POST" action="{% url 'like' post.pk %}">
35
+ {% csrf_token %}
36
+ <input type="hidden" name="next" value="{{ request.path }}">
37
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
38
+ <i class="far fa-thumbs-up"> <span>{{ post.likes.all.count }}</span></i>
39
+ </button>
40
+ </form>
41
+
42
+ <form method="POST" action="{% url 'dislike' post.pk %}">
43
+ {% csrf_token %}
44
+ <input type="hidden" name="next" value="{{ request.path }}">
45
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
46
+ <i class="far fa-thumbs-down"> <span>{{ post.dislikes.all.count }}</span></i>
47
+ </button>
48
+ </form>
49
  </div>
 
50
  </div>
51
+ {% endfor %}
52
+ </div>
53
+ {% endblock content %}
src/social/templates/social/profile.html ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'landing/base.html' %}
2
+
3
+ {% block content %}
4
+ <div class="container">
5
+ <div class="row mt-5">
6
+ <div class="col-md-3 col-sm-6">
7
+ <a href="{% url 'post-list' %}" class="btn btn-light">Back to Feed</a>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="row justify-content-center mt-5">
12
+ <div class="card shadow-sm col-md-8 col-sm-12 border-bottom px-5 pt-3">
13
+ <img src="{{ profile.picture.url }}" class="rounded-circle" width="100" height="100" />
14
+ {% if profile.name %}
15
+ <h3 class="py-4">{{ profile.name }}
16
+ <span>
17
+ {% if request.user == user %}
18
+ <a href="{% url 'profile-edit' profile.pk %}" style="color: #333"><i class="far fa-edit"></i></a>
19
+ {% endif %}
20
+ </span>
21
+ {% else %}
22
+ <h3 class="py-4">{{ user.username }}
23
+ <span>
24
+ {% if request.user == user %}
25
+ <a href="{% url 'profile-edit' profile.pk %}" style="color: #333"><i class="far fa-edit"></i></a>
26
+ {% endif %}
27
+ </span>
28
+ </h3>
29
+ {% endif %}
30
+ </h3>
31
+
32
+ {% if profile.location %}
33
+ <p>{{ profile.location }}</p>
34
+ {% endif %}
35
+
36
+ {% if profile.birth_date %}
37
+ <p>{{ profile.birth_date }}</p>
38
+ {% endif %}
39
+
40
+ {% if profile.bio %}
41
+ <p>{{ profile.bio }}</p>
42
+ {% endif %}
43
+
44
+ <div class="mb-3">
45
+ <p>Followers: {{ number_of_followers }}</p>
46
+ {% if user == request.user %}
47
+ {% else %}
48
+ {% if is_following %}
49
+ <form method="POST" action="{% url 'remove-follower' profile.pk %}">
50
+ {% csrf_token %}
51
+ <button class="btn btn-outline-danger" type="submit">Unfollow</button>
52
+ </form>
53
+ {% else %}
54
+ <form method="POST" action="{% url 'add-follower' profile.pk %}">
55
+ {% csrf_token %}
56
+ <button class="btn btn-outline-success" type="submit">Follow</button>
57
+ </form>
58
+ {% endif %}
59
+ {% endif %}
60
+ </div>
61
+ </div>
62
+ </div>
63
+
64
+ {% for post in posts %}
65
+ <div class="row justify-content-center mt-5">
66
+ <div class="col-md-8 col-sm-12 border-bottom">
67
+ <p><a style="text-decoration: none" class="text-primary" href="{% url 'profile' post.author.profile.pk %}">@{{ post.author }}</a> {{ post.created_on }}</p>
68
+ <div class="position-relative">
69
+ <p>{{ post.body }}</p>
70
+ <a href="{% url 'post-detail' post.pk %}" class="stretched-link"></a>
71
+ </div>
72
+
73
+ <div class="d-flex flex-row">
74
+ <form method="POST" action="{% url 'like' post.pk %}">
75
+ {% csrf_token %}
76
+ <input type="hidden" name="next" value="{{ request.path }}">
77
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
78
+ <i class="far fa-thumbs-up"> <span>{{ post.likes.all.count }}</span></i>
79
+ </button>
80
+ </form>
81
+
82
+ <form method="POST" action="{% url 'dislike' post.pk %}">
83
+ {% csrf_token %}
84
+ <input type="hidden" name="next" value="{{ request.path }}">
85
+ <button style="background-color: transparent; border: none; box-shadow: none;" type="submit">
86
+ <i class="far fa-thumbs-down"> <span>{{ post.dislikes.all.count }}</span></i>
87
+ </button>
88
+ </form>
89
+ </div>
90
+ </div>
91
+ </div>
92
+ {% endfor %}
93
+ </div>
94
+ {% endblock content %}
src/social/templates/social/profile_edit.html ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {% extends 'landing/base.html' %}
2
+ {% load crispy_forms_tags %}
3
+
4
+ {% block content %}
5
+ <div class="container">
6
+ <div class="row mt-5">
7
+ <div class="col-md-5 col-sm-6">
8
+ <a href="{% url 'profile' object.pk %}" class="btn btn-light">Back To Profile</a>
9
+ </div>
10
+ </div>
11
+
12
+ <div class="row justify-content-center mt-3">
13
+ <div class="col-md-5 col-sm-12 border-bottom">
14
+ <h5>Update Your Profile</h5>
15
+ </div>
16
+ </div>
17
+
18
+ <div class="row justify-content-center mt-3 mb-5">
19
+ <div class="col-md-5 col-sm-12 border-bottom">
20
+ <form method="POST" enctype="multipart/form-data">
21
+ {% csrf_token %}
22
+ {{ form | crispy }}
23
+ <div class="d-grid gap-2">
24
+ <button class="btn btn-success mt-3">Submit!</button>
25
+ </div>
26
+ </form>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ {% endblock content %}
src/social/urls.py CHANGED
@@ -1,5 +1,5 @@
1
  from django.urls import path
2
- from .views import PostListView, PostDetailView, PostEditView, PostDeleteView, CommentDeleteView
3
 
4
  urlpatterns = [
5
  path('', PostListView.as_view(), name='post-list'),
@@ -7,4 +7,12 @@ urlpatterns = [
7
  path('post/edit/<int:pk>/', PostEditView.as_view(), name='post-edit'),
8
  path('post/delete/<int:pk>/', PostDeleteView.as_view(), name='post-delete'),
9
  path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
 
 
 
 
 
 
 
 
10
  ]
 
1
  from django.urls import path
2
+ from .views import PostListView, PostDetailView, PostEditView, PostDeleteView, CommentDeleteView, ProfileView, ProfileEditView, AddFollower, RemoveFollower, AddLike, AddDislike
3
 
4
  urlpatterns = [
5
  path('', PostListView.as_view(), name='post-list'),
 
7
  path('post/edit/<int:pk>/', PostEditView.as_view(), name='post-edit'),
8
  path('post/delete/<int:pk>/', PostDeleteView.as_view(), name='post-delete'),
9
  path('post/<int:post_pk>/comment/delete/<int:pk>/', CommentDeleteView.as_view(), name='comment-delete'),
10
+ path('post/<int:pk>/like', AddLike.as_view(), name='like'),
11
+ path('post/<int:pk>/dislike', AddDislike.as_view(), name='dislike'),
12
+ path('profile/<int:pk>/', ProfileView.as_view(), name='profile'),
13
+ path('profile/edit/<int:pk>/', ProfileEditView.as_view(), name='profile-edit'),
14
+ path('profile/<int:pk>/followers/add', AddFollower.as_view(), name='add-follower'),
15
+ path('profile/<int:pk>/followers/remove', RemoveFollower.as_view(), name='remove-follower'),
16
+
17
+
18
  ]
src/social/views.py CHANGED
@@ -1,10 +1,11 @@
1
- from django.shortcuts import render
2
  from django.urls import reverse_lazy
 
3
  from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
4
  from django.views import View
5
- from django.views.generic.edit import UpdateView, DeleteView
6
- from .models import Post, Comment
7
  from .forms import PostForm, CommentForm
 
8
 
9
 
10
  class PostListView(LoginRequiredMixin, View):
@@ -16,6 +17,7 @@ class PostListView(LoginRequiredMixin, View):
16
  'post_list': posts,
17
  'form': form,
18
  }
 
19
  return render(request, 'social/post_list.html', context)
20
 
21
  def post(self, request, *args, **kwargs):
@@ -31,12 +33,14 @@ class PostListView(LoginRequiredMixin, View):
31
  'post_list': posts,
32
  'form': form,
33
  }
 
34
  return render(request, 'social/post_list.html', context)
35
 
36
  class PostDetailView(LoginRequiredMixin, View):
37
  def get(self, request, pk, *args, **kwargs):
38
  post = Post.objects.get(pk=pk)
39
  form = CommentForm()
 
40
  comments = Comment.objects.filter(post=post).order_by('-created_on')
41
 
42
  context = {
@@ -46,7 +50,6 @@ class PostDetailView(LoginRequiredMixin, View):
46
  }
47
 
48
  return render(request, 'social/post_detail.html', context)
49
-
50
  def post(self, request, pk, *args, **kwargs):
51
  post = Post.objects.get(pk=pk)
52
  form = CommentForm(request.POST)
@@ -56,7 +59,7 @@ class PostDetailView(LoginRequiredMixin, View):
56
  new_comment.author = request.user
57
  new_comment.post = post
58
  new_comment.save()
59
-
60
  comments = Comment.objects.filter(post=post).order_by('-created_on')
61
 
62
  context = {
@@ -71,11 +74,11 @@ class PostEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
71
  model = Post
72
  fields = ['body']
73
  template_name = 'social/post_edit.html'
74
-
75
  def get_success_url(self):
76
  pk = self.kwargs['pk']
77
  return reverse_lazy('post-detail', kwargs={'pk': pk})
78
-
79
  def test_func(self):
80
  post = self.get_object()
81
  return self.request.user == post.author
@@ -98,5 +101,122 @@ class CommentDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
98
  return reverse_lazy('post-detail', kwargs={'pk': pk})
99
 
100
  def test_func(self):
101
- comment = self.get_object()
102
- return self.request.user == comment.author
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from django.shortcuts import render, redirect
2
  from django.urls import reverse_lazy
3
+ from django.http import HttpResponseRedirect
4
  from django.contrib.auth.mixins import UserPassesTestMixin, LoginRequiredMixin
5
  from django.views import View
6
+ from .models import Post, Comment, UserProfile
 
7
  from .forms import PostForm, CommentForm
8
+ from django.views.generic.edit import UpdateView, DeleteView
9
 
10
 
11
  class PostListView(LoginRequiredMixin, View):
 
17
  'post_list': posts,
18
  'form': form,
19
  }
20
+
21
  return render(request, 'social/post_list.html', context)
22
 
23
  def post(self, request, *args, **kwargs):
 
33
  'post_list': posts,
34
  'form': form,
35
  }
36
+
37
  return render(request, 'social/post_list.html', context)
38
 
39
  class PostDetailView(LoginRequiredMixin, View):
40
  def get(self, request, pk, *args, **kwargs):
41
  post = Post.objects.get(pk=pk)
42
  form = CommentForm()
43
+
44
  comments = Comment.objects.filter(post=post).order_by('-created_on')
45
 
46
  context = {
 
50
  }
51
 
52
  return render(request, 'social/post_detail.html', context)
 
53
  def post(self, request, pk, *args, **kwargs):
54
  post = Post.objects.get(pk=pk)
55
  form = CommentForm(request.POST)
 
59
  new_comment.author = request.user
60
  new_comment.post = post
61
  new_comment.save()
62
+
63
  comments = Comment.objects.filter(post=post).order_by('-created_on')
64
 
65
  context = {
 
74
  model = Post
75
  fields = ['body']
76
  template_name = 'social/post_edit.html'
77
+
78
  def get_success_url(self):
79
  pk = self.kwargs['pk']
80
  return reverse_lazy('post-detail', kwargs={'pk': pk})
81
+
82
  def test_func(self):
83
  post = self.get_object()
84
  return self.request.user == post.author
 
101
  return reverse_lazy('post-detail', kwargs={'pk': pk})
102
 
103
  def test_func(self):
104
+ post = self.get_object()
105
+ return self.request.user == post.author
106
+
107
+ class ProfileView(View):
108
+ def get(self, request, pk, *args, **kwargs):
109
+ profile = UserProfile.objects.get(pk=pk)
110
+ user = profile.user
111
+ posts = Post.objects.filter(author=user).order_by('-created_on')
112
+
113
+ followers = profile.followers.all()
114
+
115
+ if len(followers) == 0:
116
+ is_following = False
117
+
118
+ for follower in followers:
119
+ if follower == request.user:
120
+ is_following = True
121
+ break
122
+ else:
123
+ is_following = False
124
+
125
+ number_of_followers = len(followers)
126
+
127
+ context = {
128
+ 'user': user,
129
+ 'profile': profile,
130
+ 'posts': posts,
131
+ 'number_of_followers': number_of_followers,
132
+ 'is_following': is_following,
133
+ }
134
+
135
+ return render(request, 'social/profile.html', context)
136
+
137
+ class ProfileEditView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
138
+ model = UserProfile
139
+ fields = ['name', 'bio', 'birth_date', 'location', 'picture']
140
+ template_name = 'social/profile_edit.html'
141
+
142
+ def get_success_url(self):
143
+ pk = self.kwargs['pk']
144
+ return reverse_lazy('profile', kwargs={'pk': pk})
145
+
146
+ def test_func(self):
147
+ profile = self.get_object()
148
+ return self.request.user == profile.user
149
+
150
+ class AddFollower(LoginRequiredMixin, View):
151
+ def post(self, request, pk, *args, **kwargs):
152
+ profile = UserProfile.objects.get(pk=pk)
153
+ profile.followers.add(request.user)
154
+
155
+ return redirect('profile', pk=profile.pk)
156
+
157
+ class RemoveFollower(LoginRequiredMixin, View):
158
+ def post(self, request, pk, *args, **kwargs):
159
+ profile = UserProfile.objects.get(pk=pk)
160
+ profile.followers.remove(request.user)
161
+
162
+ return redirect('profile', pk=profile.pk)
163
+
164
+ class AddLike(LoginRequiredMixin, View):
165
+ def post(self, request, pk, *args, **kwargs):
166
+ post = Post.objects.get(pk=pk)
167
+
168
+ is_dislike = False
169
+
170
+ for dislike in post.dislikes.all():
171
+ if dislike == request.user:
172
+ is_dislike = True
173
+ break
174
+
175
+ if is_dislike:
176
+ post.dislikes.remove(request.user)
177
+
178
+ is_like = False
179
+
180
+ for like in post.likes.all():
181
+ if like == request.user:
182
+ is_like = True
183
+ break
184
+
185
+ if not is_like:
186
+ post.likes.add(request.user)
187
+
188
+ if is_like:
189
+ post.likes.remove(request.user)
190
+
191
+ next = request.POST.get('next', '/')
192
+ return HttpResponseRedirect(next)
193
+
194
+ class AddDislike(LoginRequiredMixin, View):
195
+ def post(self, request, pk, *args, **kwargs):
196
+ post = Post.objects.get(pk=pk)
197
+
198
+ is_like = False
199
+
200
+ for like in post.likes.all():
201
+ if like == request.user:
202
+ is_like = True
203
+ break
204
+
205
+ if is_like:
206
+ post.likes.remove(request.user)
207
+
208
+ is_dislike = False
209
+
210
+ for dislike in post.dislikes.all():
211
+ if dislike == request.user:
212
+ is_dislike = True
213
+ break
214
+
215
+ if not is_dislike:
216
+ post.dislikes.add(request.user)
217
+
218
+ if is_dislike:
219
+ post.dislikes.remove(request.user)
220
+
221
+ next = request.POST.get('next', '/')
222
+ return HttpResponseRedirect(next)