├── groups ├── __init__.py ├── migrations │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── 0001_initial.cpython-311.pyc │ │ └── 0002_group_category_group_tags_group_visibility_and_more.cpython-311.pyc │ ├── 0002_group_category_group_tags_group_visibility_and_more.py │ └── 0001_initial.py ├── tests.py ├── __pycache__ │ ├── admin.cpython-311.pyc │ ├── apps.cpython-311.pyc │ ├── forms.cpython-311.pyc │ ├── urls.cpython-311.pyc │ ├── views.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ └── models.cpython-311.pyc ├── apps.py ├── admin.py ├── forms.py ├── urls.py ├── models.py └── templates │ └── groups │ ├── create_group.html │ ├── manage_requests.html │ ├── group_detail.html │ ├── groups.html │ └── group_messages.html ├── social ├── __init__.py ├── migrations │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ └── 0001_initial.cpython-311.pyc │ └── 0001_initial.py ├── tests.py ├── __pycache__ │ ├── admin.cpython-311.pyc │ ├── apps.cpython-311.pyc │ ├── forms.cpython-311.pyc │ ├── urls.cpython-311.pyc │ ├── views.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ └── models.cpython-311.pyc ├── apps.py ├── templates │ └── social │ │ ├── home.html │ │ ├── post_viewers.html │ │ └── post_create.html ├── forms.py ├── urls.py ├── admin.py ├── models.py └── views.py ├── tmp └── restart.txt ├── users ├── __init__.py ├── migrations │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── 0001_initial.cpython-311.pyc │ │ ├── 0002_user_followers.cpython-311.pyc │ │ └── 0003_user_theme_preference.cpython-311.pyc │ ├── 0003_user_theme_preference.py │ ├── 0002_user_followers.py │ └── 0001_initial.py ├── tests.py ├── __pycache__ │ ├── apps.cpython-311.pyc │ ├── urls.cpython-311.pyc │ ├── admin.cpython-311.pyc │ ├── forms.cpython-311.pyc │ ├── models.cpython-311.pyc │ ├── utils.cpython-311.pyc │ ├── views.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ └── context_processors.cpython-311.pyc ├── apps.py ├── templates │ └── users │ │ ├── login.html │ │ ├── user_following.html │ │ ├── purchase_premium.html │ │ ├── user_followers.html │ │ ├── profile.html │ │ ├── settings.html │ │ ├── edit_profile.html │ │ ├── register.html │ │ └── premium_dashboard.html ├── context_processors.py ├── admin.py ├── models.py ├── forms.py ├── utils.py └── urls.py ├── messaging ├── __init__.py ├── migrations │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ └── 0001_initial.cpython-311.pyc │ └── 0001_initial.py ├── tests.py ├── __pycache__ │ ├── admin.cpython-311.pyc │ ├── apps.cpython-311.pyc │ ├── urls.cpython-311.pyc │ ├── views.cpython-311.pyc │ ├── models.cpython-311.pyc │ └── __init__.cpython-311.pyc ├── apps.py ├── admin.py ├── urls.py ├── models.py └── views.py ├── notifications ├── __init__.py ├── migrations │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── 0001_initial.cpython-311.pyc │ │ ├── 0003_alter_notification_user.cpython-311.pyc │ │ └── 0002_notification_notification_type_and_more.cpython-311.pyc │ ├── 0003_alter_notification_user.py │ ├── 0002_notification_notification_type_and_more.py │ └── 0001_initial.py ├── tests.py ├── __pycache__ │ ├── admin.cpython-311.pyc │ ├── apps.cpython-311.pyc │ ├── models.cpython-311.pyc │ ├── urls.cpython-311.pyc │ ├── views.cpython-311.pyc │ ├── __init__.cpython-311.pyc │ └── context_processors.cpython-311.pyc ├── apps.py ├── context_processors.py ├── admin.py ├── urls.py ├── models.py ├── views.py └── templates │ └── notifications │ └── notifications.html ├── jooustconnectprod ├── __init__.py ├── __pycache__ │ ├── urls.cpython-311.pyc │ ├── urls.cpython-39.pyc │ ├── wsgi.cpython-311.pyc │ ├── wsgi.cpython-39.pyc │ ├── __init__.cpython-39.pyc │ ├── settings.cpython-39.pyc │ ├── __init__.cpython-311.pyc │ └── settings.cpython-311.pyc ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── .gitignore ├── requirements.txt ├── __pycache__ └── passenger_wsgi.cpython-39.pyc ├── templates └── admin │ └── base_site.html ├── passenger_wsgi.py ├── manage.py ├── LICENSE └── README.md /groups/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /social/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/restart.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /users/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /messaging/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notifications/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /users/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /groups/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /jooustconnectprod/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /messaging/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /social/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /notifications/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | media/ 2 | db.sqlite3 3 | staticfiles/ 4 | static/ 5 | -------------------------------------------------------------------------------- /groups/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /social/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /messaging/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /notifications/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | daphne 3 | channels 4 | django-widget-tweaks 5 | python-dateutil 6 | pillow 7 | django-cors-headers 8 | -------------------------------------------------------------------------------- /users/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/forms.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/forms.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/forms.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/forms.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/forms.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/forms.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/utils.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/utils.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /__pycache__/passenger_wsgi.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/__pycache__/passenger_wsgi.cpython-39.pyc -------------------------------------------------------------------------------- /groups/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /groups/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /social/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /users/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/urls.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/urls.cpython-39.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/wsgi.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/wsgi.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/wsgi.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/wsgi.cpython-39.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/__init__.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/__init__.cpython-39.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/settings.cpython-39.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/settings.cpython-39.pyc -------------------------------------------------------------------------------- /users/__pycache__/context_processors.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/__pycache__/context_processors.cpython-311.pyc -------------------------------------------------------------------------------- /users/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /groups/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /jooustconnectprod/__pycache__/settings.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/jooustconnectprod/__pycache__/settings.cpython-311.pyc -------------------------------------------------------------------------------- /social/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /groups/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /social/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/social/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /users/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/__pycache__/context_processors.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/__pycache__/context_processors.cpython-311.pyc -------------------------------------------------------------------------------- /messaging/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/messaging/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /users/migrations/__pycache__/0002_user_followers.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/migrations/__pycache__/0002_user_followers.cpython-311.pyc -------------------------------------------------------------------------------- /users/migrations/__pycache__/0003_user_theme_preference.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/users/migrations/__pycache__/0003_user_theme_preference.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/migrations/__pycache__/0003_alter_notification_user.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/migrations/__pycache__/0003_alter_notification_user.cpython-311.pyc -------------------------------------------------------------------------------- /notifications/migrations/__pycache__/0002_notification_notification_type_and_more.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/notifications/migrations/__pycache__/0002_notification_notification_type_and_more.cpython-311.pyc -------------------------------------------------------------------------------- /groups/migrations/__pycache__/0002_group_category_group_tags_group_visibility_and_more.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/phantom-kali/JooustConnect/HEAD/groups/migrations/__pycache__/0002_group_category_group_tags_group_visibility_and_more.cpython-311.pyc -------------------------------------------------------------------------------- /users/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UsersConfig(AppConfig): 5 | <<<<<<< HEAD 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "users" 8 | ======= 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'users' 11 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 12 | -------------------------------------------------------------------------------- /groups/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class GroupsConfig(AppConfig): 5 | <<<<<<< HEAD 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "groups" 8 | ======= 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'groups' 11 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 12 | -------------------------------------------------------------------------------- /social/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class SocialConfig(AppConfig): 5 | <<<<<<< HEAD 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "social" 8 | ======= 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'social' 11 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 12 | -------------------------------------------------------------------------------- /messaging/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class MessagingConfig(AppConfig): 5 | <<<<<<< HEAD 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "messaging" 8 | ======= 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'messaging' 11 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 12 | -------------------------------------------------------------------------------- /templates/admin/base_site.html: -------------------------------------------------------------------------------- 1 | {% extends "admin/base.html" %} 2 | 3 | {% block title %}{% if subtitle %}{{ subtitle }} | {% endif %}{{ title }} | JooustConnect Admin{% endblock %} 4 | 5 | {% block branding %} 6 |

JooustConnect Administration

7 | {% endblock %} 8 | 9 | {% block nav-global %}{% endblock %} -------------------------------------------------------------------------------- /notifications/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class NotificationsConfig(AppConfig): 5 | <<<<<<< HEAD 6 | default_auto_field = "django.db.models.BigAutoField" 7 | name = "notifications" 8 | ======= 9 | default_auto_field = 'django.db.models.BigAutoField' 10 | name = 'notifications' 11 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 12 | -------------------------------------------------------------------------------- /notifications/context_processors.py: -------------------------------------------------------------------------------- 1 | def notification_count(request): 2 | if request.user.is_authenticated: 3 | count = request.user.notifications.filter(is_read=False).count() 4 | <<<<<<< HEAD 5 | return {"unread_notification_count": count} 6 | return {"unread_notification_count": 0} 7 | ======= 8 | return {'unread_notification_count': count} 9 | return {'unread_notification_count': 0} 10 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 11 | -------------------------------------------------------------------------------- /social/templates/social/home.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Welcome to JooustConnect

6 |

Connect with fellow students, share ideas, and stay updated.

7 |
8 |

Join our community today!

9 | Sign Up 10 | Log In 11 |
12 | {% endblock %} -------------------------------------------------------------------------------- /jooustconnectprod/asgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.core.asgi import get_asgi_application 3 | from channels.routing import ProtocolTypeRouter, URLRouter 4 | from channels.auth import AuthMiddlewareStack 5 | from django.conf import settings 6 | 7 | <<<<<<< HEAD 8 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jooustconnectprod.settings") 9 | 10 | django_asgi_app = get_asgi_application() 11 | ======= 12 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jooustconnectprod.settings') 13 | 14 | django_asgi_app = get_asgi_application() 15 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 16 | -------------------------------------------------------------------------------- /users/migrations/0003_user_theme_preference.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-04 06:50 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("users", "0002_user_followers"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="user", 15 | name="theme_preference", 16 | field=models.CharField( 17 | choices=[("light", "Light"), ("dark", "Dark"), ("system", "System")], 18 | default="system", 19 | max_length=10, 20 | ), 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /notifications/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Notification 3 | 4 | 5 | @admin.register(Notification) 6 | class NotificationAdmin(admin.ModelAdmin): 7 | <<<<<<< HEAD 8 | list_display = ("user", "content", "is_read", "timestamp") 9 | search_fields = ("user__username", "content") 10 | list_filter = ("is_read", "timestamp") 11 | ordering = ("-timestamp",) 12 | ======= 13 | list_display = ('user', 'content', 'is_read', 'timestamp') 14 | search_fields = ('user__username', 'content') 15 | list_filter = ('is_read', 'timestamp') 16 | ordering = ('-timestamp',) 17 | 18 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 19 | -------------------------------------------------------------------------------- /jooustconnectprod/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for jooustconnect 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/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | <<<<<<< HEAD 15 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jooustconnectprod.settings") 16 | 17 | application = get_wsgi_application() 18 | ======= 19 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jooustconnectprod.settings') 20 | 21 | application = get_wsgi_application() 22 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 23 | -------------------------------------------------------------------------------- /passenger_wsgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from importlib import import_module 4 | from django.core.wsgi import get_wsgi_application 5 | 6 | # Add the project directory to the system path 7 | sys.path.insert(0, os.path.dirname(__file__)) 8 | 9 | # Set the default settings module 10 | <<<<<<< HEAD 11 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jooustconnectprod.settings") 12 | 13 | # Import the Django WSGI application 14 | application = get_wsgi_application() 15 | ======= 16 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'jooustconnectprod.settings') 17 | 18 | # Import the Django WSGI application 19 | application = get_wsgi_application() 20 | 21 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 22 | -------------------------------------------------------------------------------- /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", "jooustconnectprod.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 | <<<<<<< HEAD 16 | execute_from_command_line(sys.argv) 17 | ======= 18 | execute_from_command_line(sys.argv) 19 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 20 | -------------------------------------------------------------------------------- /messaging/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Message 3 | 4 | <<<<<<< HEAD 5 | 6 | @admin.register(Message) 7 | class MessageAdmin(admin.ModelAdmin): 8 | list_display = ("sender", "receiver", "content", "timestamp") 9 | search_fields = ("sender__username", "receiver__username", "content") 10 | list_filter = ("timestamp",) 11 | ordering = ("-timestamp",) 12 | ======= 13 | @admin.register(Message) 14 | class MessageAdmin(admin.ModelAdmin): 15 | list_display = ('sender', 'receiver', 'content', 'timestamp') 16 | search_fields = ('sender__username', 'receiver__username', 'content') 17 | list_filter = ('timestamp',) 18 | ordering = ('-timestamp',) 19 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 20 | -------------------------------------------------------------------------------- /social/templates/social/post_viewers.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Viewers for Post: {{ post.content|truncatewords:10 }}

6 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 | 15 | 24 |
25 | {% endblock %} 26 | -------------------------------------------------------------------------------- /users/templates/users/login.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 |

Login

7 |
8 | {% csrf_token %} 9 | {% for field in form %} 10 |
11 | {{ field.label_tag }} 12 | {{ field }} 13 | {% if field.errors %} 14 |
15 | {{ field.errors }} 16 |
17 | {% endif %} 18 |
19 | {% endfor %} 20 | 21 |
22 |

Don't have an account? Register here

23 |
24 |
25 | {% endblock %} -------------------------------------------------------------------------------- /users/migrations/0002_user_followers.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 17:45 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 | <<<<<<< HEAD 11 | ("users", "0001_initial"), 12 | ======= 13 | ('users', '0001_initial'), 14 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 15 | ] 16 | 17 | operations = [ 18 | migrations.AddField( 19 | <<<<<<< HEAD 20 | model_name="user", 21 | name="followers", 22 | field=models.ManyToManyField( 23 | related_name="following", to=settings.AUTH_USER_MODEL 24 | ), 25 | ======= 26 | model_name='user', 27 | name='followers', 28 | field=models.ManyToManyField(related_name='following', to=settings.AUTH_USER_MODEL), 29 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /notifications/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | <<<<<<< HEAD 6 | path("", views.notifications, name="notifications"), 7 | path("poll/", views.poll_notifications, name="poll"), 8 | path( 9 | "mark-read//", 10 | views.mark_notification_read, 11 | name="mark_notification_read", 12 | ), 13 | path( 14 | "mark-all-read/", 15 | views.mark_all_notifications_read, 16 | name="mark_all_notifications_read", 17 | ), 18 | ] 19 | ======= 20 | path('', views.notifications, name='notifications'), 21 | path('poll/', views.poll_notifications, name='poll'), 22 | path('mark-read//', views.mark_notification_read, name='mark_notification_read'), 23 | path('mark-all-read/', views.mark_all_notifications_read, name='mark_all_notifications_read'), 24 | ] 25 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 26 | -------------------------------------------------------------------------------- /messaging/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | <<<<<<< HEAD 6 | path("", views.messages, name="messages"), 7 | path("/", views.messages, name="message_detail"), 8 | # Keep your existing URL patterns 9 | path("get-messages//", views.get_messages, name="get_messages"), 10 | path("poll-messages//", views.poll_messages, name="poll_messages"), 11 | path("send-message/", views.send_message, name="send_message"), 12 | ] 13 | ======= 14 | path('', views.messages, name='messages'), 15 | path('/', views.messages, name='message_detail'), 16 | # Keep your existing URL patterns 17 | path('get-messages//', views.get_messages, name='get_messages'), 18 | path('poll-messages//', views.poll_messages, name='poll_messages'), 19 | path('send-message/', views.send_message, name='send_message'), 20 | ] 21 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 22 | -------------------------------------------------------------------------------- /messaging/models.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.db import models 3 | 4 | <<<<<<< HEAD 5 | 6 | class Message(models.Model): 7 | sender = models.ForeignKey( 8 | settings.AUTH_USER_MODEL, related_name="sent_messages", on_delete=models.CASCADE 9 | ) 10 | receiver = models.ForeignKey( 11 | settings.AUTH_USER_MODEL, 12 | related_name="received_messages", 13 | on_delete=models.CASCADE, 14 | ) 15 | ======= 16 | class Message(models.Model): 17 | sender = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='sent_messages', on_delete=models.CASCADE) 18 | receiver = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='received_messages', on_delete=models.CASCADE) 19 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 20 | content = models.TextField() 21 | timestamp = models.DateTimeField(auto_now_add=True) 22 | 23 | class Meta: 24 | <<<<<<< HEAD 25 | ordering = ["timestamp"] 26 | ======= 27 | ordering = ['timestamp'] 28 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Fidel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /users/context_processors.py: -------------------------------------------------------------------------------- 1 | from django.utils import timezone 2 | 3 | <<<<<<< HEAD 4 | 5 | def premium_status(request): 6 | if request.user.is_authenticated: 7 | is_premium = ( 8 | request.user.is_premium 9 | and request.user.premium_expiry is not None 10 | and request.user.premium_expiry > timezone.now() 11 | ) 12 | days_left = ( 13 | (request.user.premium_expiry - timezone.now()).days if is_premium else 0 14 | ) 15 | return {"is_premium": is_premium, "premium_days_left": days_left} 16 | return {"is_premium": False, "premium_days_left": 0} 17 | ======= 18 | def premium_status(request): 19 | if request.user.is_authenticated: 20 | is_premium = request.user.is_premium and request.user.premium_expiry is not None and request.user.premium_expiry > timezone.now() 21 | days_left = (request.user.premium_expiry - timezone.now()).days if is_premium else 0 22 | return { 23 | 'is_premium': is_premium, 24 | 'premium_days_left': days_left 25 | } 26 | return {'is_premium': False, 'premium_days_left': 0} 27 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 28 | -------------------------------------------------------------------------------- /social/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import User, Post 3 | 4 | <<<<<<< HEAD 5 | 6 | class PostForm(forms.ModelForm): 7 | class Meta: 8 | model = Post 9 | fields = ["content", "image", "video"] 10 | widgets = { 11 | "content": forms.Textarea( 12 | attrs={ 13 | "class": "form-control", 14 | "rows": 3, 15 | "placeholder": "What's on your mind?", 16 | } 17 | ), 18 | "image": forms.FileInput(attrs={"class": "form-control-file"}), 19 | "video": forms.FileInput(attrs={"class": "form-control-file"}), 20 | } 21 | ======= 22 | class PostForm(forms.ModelForm): 23 | class Meta: 24 | model = Post 25 | fields = ['content', 'image', 'video'] 26 | widgets = { 27 | 'content': forms.Textarea(attrs={'class': 'form-control', 'rows': 3, 'placeholder': "What's on your mind?"}), 28 | 'image': forms.FileInput(attrs={'class': 'form-control-file'}), 29 | 'video': forms.FileInput(attrs={'class': 'form-control-file'}), 30 | } 31 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 32 | -------------------------------------------------------------------------------- /notifications/migrations/0003_alter_notification_user.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-03 11:47 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 | <<<<<<< HEAD 13 | ("notifications", "0002_notification_notification_type_and_more"), 14 | ======= 15 | ('notifications', '0002_notification_notification_type_and_more'), 16 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 17 | ] 18 | 19 | operations = [ 20 | migrations.AlterField( 21 | <<<<<<< HEAD 22 | model_name="notification", 23 | name="user", 24 | field=models.ForeignKey( 25 | on_delete=django.db.models.deletion.CASCADE, 26 | related_name="notifications", 27 | to=settings.AUTH_USER_MODEL, 28 | ), 29 | ======= 30 | model_name='notification', 31 | name='user', 32 | field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='notifications', to=settings.AUTH_USER_MODEL), 33 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /notifications/migrations/0002_notification_notification_type_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-03 06:28 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | <<<<<<< HEAD 10 | ("notifications", "0001_initial"), 11 | ======= 12 | ('notifications', '0001_initial'), 13 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 14 | ] 15 | 16 | operations = [ 17 | migrations.AddField( 18 | <<<<<<< HEAD 19 | model_name="notification", 20 | name="notification_type", 21 | field=models.CharField( 22 | choices=[("DM", "Direct Message"), ("GROUP", "Group Message")], 23 | default="DM", 24 | max_length=5, 25 | ), 26 | ), 27 | migrations.AddField( 28 | model_name="notification", 29 | name="related_id", 30 | ======= 31 | model_name='notification', 32 | name='notification_type', 33 | field=models.CharField(choices=[('DM', 'Direct Message'), ('GROUP', 'Group Message')], default='DM', max_length=5), 34 | ), 35 | migrations.AddField( 36 | model_name='notification', 37 | name='related_id', 38 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 39 | field=models.IntegerField(default=0), 40 | preserve_default=False, 41 | ), 42 | ] 43 | -------------------------------------------------------------------------------- /users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import User, MpesaTransaction 3 | 4 | <<<<<<< HEAD 5 | 6 | @admin.register(User) 7 | class UserAdmin(admin.ModelAdmin): 8 | list_display = ( 9 | "username", 10 | "nickname", 11 | "email", 12 | "course", 13 | "year", 14 | "profile_picture", 15 | ) 16 | search_fields = ("username", "nickname", "email") 17 | list_filter = ("is_staff", "is_active") 18 | ordering = ("username",) 19 | ======= 20 | @admin.register(User) 21 | class UserAdmin(admin.ModelAdmin): 22 | list_display = ('username', 'nickname', 'email', 'course', 'year', 'profile_picture') 23 | search_fields = ('username', 'nickname', 'email') 24 | list_filter = ('is_staff', 'is_active') 25 | ordering = ('username',) 26 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 27 | 28 | 29 | @admin.register(MpesaTransaction) 30 | class MpesaTransactionAdmin(admin.ModelAdmin): 31 | <<<<<<< HEAD 32 | list_display = ("transaction_id", "amount", "phone_number", "transaction_date") 33 | search_fields = ("transaction_id", "phone_number", "amount") 34 | list_filter = ("transaction_date", "amount") 35 | ordering = ("-transaction_date",) 36 | ======= 37 | list_display = ('transaction_id', 'amount', 'phone_number','transaction_date') 38 | search_fields = ('transaction_id', 'phone_number', 'amount') 39 | list_filter = ('transaction_date', 'amount') 40 | ordering = ('-transaction_date',) 41 | 42 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 43 | -------------------------------------------------------------------------------- /social/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | <<<<<<< HEAD 6 | path("", views.home, name="home"), 7 | path("feed/", views.feed, name="feed"), 8 | path("post/create/", views.create_post, name="create_post"), 9 | path( 10 | "post//increment_view/", 11 | views.increment_view_count, 12 | name="increment_view_count", 13 | ), 14 | path("post//like/", views.like_post, name="like_post"), 15 | path("post//comment/", views.add_comment, name="add_comment"), 16 | path("delete_post//", views.delete_post, name="delete_post"), 17 | path("report_post//", views.report_post, name="report_post"), 18 | path( 19 | "api/post///", 20 | views.post_details, 21 | name="post_details", 22 | ), 23 | path("post//viewers/", views.post_viewers, name="post_viewers"), 24 | ] 25 | ======= 26 | path('', views.home, name='home'), 27 | path('feed/', views.feed, name='feed'), 28 | path('post/create/', views.create_post, name='create_post'), 29 | path('post//increment_view/', views.increment_view_count, name='increment_view_count'), 30 | path('post//like/', views.like_post, name='like_post'), 31 | path('post//comment/', views.add_comment, name='add_comment'), 32 | path('delete_post//', views.delete_post, name='delete_post'), 33 | path('report_post//', views.report_post, name='report_post'), 34 | path('api/post///', views.post_details, name='post_details'), 35 | path('post//viewers/', views.post_viewers, name='post_viewers'), 36 | ] 37 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 38 | -------------------------------------------------------------------------------- /users/templates/users/user_following.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Users {{ user.username }} is Following

6 |
7 | {% for followed_user in following %} 8 |
9 |
10 | {% if followed_user.profile_picture %} 11 | {{ followed_user.username }} 13 | {% else %} 14 | {{ followed_user.username }} 16 | {% endif %} 17 |
18 | {{ followed_user.username }} 19 |
20 | {% if followed_user.is_verified %} 21 | 22 | {% endif %} 23 |
24 | {% if request.user != followed_user %} 25 |
26 | {% csrf_token %} 27 | 28 |
29 | {% endif %} 30 |
31 | {% empty %} 32 |
Not following anyone
33 | {% endfor %} 34 |
35 |
36 | {% endblock %} -------------------------------------------------------------------------------- /jooustconnectprod/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | from django.conf import settings 4 | from django.views.static import serve 5 | from django.urls import re_path 6 | 7 | urlpatterns = [ 8 | <<<<<<< HEAD 9 | path("bungu/", admin.site.urls), 10 | path("", include("social.urls")), 11 | path("users/", include("users.urls")), 12 | path("messaging/", include("messaging.urls")), 13 | path("groups/", include("groups.urls")), 14 | path("notifications/", include("notifications.urls")), 15 | ======= 16 | path('bungu/', admin.site.urls), 17 | path('', include('social.urls')), 18 | path('users/', include('users.urls')), 19 | path('messaging/', include('messaging.urls')), 20 | path('groups/', include('groups.urls')), 21 | path('notifications/', include('notifications.urls')), 22 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 23 | ] 24 | 25 | if not settings.DEBUG: 26 | urlpatterns += [ 27 | <<<<<<< HEAD 28 | re_path(r"^media/(?P.*)$", serve, {"document_root": settings.MEDIA_ROOT}), 29 | re_path( 30 | r"^static/(?P.*)$", serve, {"document_root": settings.STATIC_ROOT} 31 | ), 32 | ] 33 | else: 34 | from django.conf.urls.static import static 35 | 36 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 37 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 38 | ======= 39 | re_path(r'^media/(?P.*)$', serve, {'document_root': settings.MEDIA_ROOT}), 40 | re_path(r'^static/(?P.*)$', serve, {'document_root': settings.STATIC_ROOT}), 41 | ] 42 | else: 43 | from django.conf.urls.static import static 44 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 45 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 46 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 47 | -------------------------------------------------------------------------------- /notifications/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 11:45 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 | <<<<<<< HEAD 19 | name="Notification", 20 | fields=[ 21 | ( 22 | "id", 23 | models.BigAutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("content", models.CharField(max_length=255)), 31 | ("is_read", models.BooleanField(default=False)), 32 | ("timestamp", models.DateTimeField(auto_now_add=True)), 33 | ( 34 | "user", 35 | models.ForeignKey( 36 | on_delete=django.db.models.deletion.CASCADE, 37 | to=settings.AUTH_USER_MODEL, 38 | ), 39 | ), 40 | ======= 41 | name='Notification', 42 | fields=[ 43 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 44 | ('content', models.CharField(max_length=255)), 45 | ('is_read', models.BooleanField(default=False)), 46 | ('timestamp', models.DateTimeField(auto_now_add=True)), 47 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 48 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 49 | ], 50 | ), 51 | ] 52 | -------------------------------------------------------------------------------- /users/models.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AbstractUser 2 | from django.db import models 3 | from django.conf import settings 4 | 5 | from django.utils import timezone 6 | from datetime import timedelta 7 | 8 | 9 | class User(AbstractUser): 10 | nickname = models.CharField(max_length=50, blank=True) 11 | course = models.CharField(max_length=100, blank=True) # Make this optional 12 | year = models.IntegerField(null=True, blank=True) # Make this optional 13 | profile_picture = models.ImageField(upload_to="profile_pics/", blank=True) 14 | bio = models.TextField(blank=True) 15 | 16 | followers = models.ManyToManyField( 17 | "self", symmetrical=False, related_name="following" 18 | ) 19 | 20 | is_verified = models.BooleanField(default=False) 21 | is_premium = models.BooleanField(default=False) 22 | premium_expiry = models.DateTimeField(null=True, blank=True) 23 | 24 | privacy_dms = models.BooleanField(default=False) 25 | privacy_posts = models.BooleanField(default=False) 26 | 27 | THEME_CHOICES = [ 28 | ("light", "Light"), 29 | ("dark", "Dark"), 30 | ("system", "System"), 31 | ] 32 | theme_preference = models.CharField( 33 | max_length=10, choices=THEME_CHOICES, default="system" 34 | ) 35 | 36 | def premium_expiring_soon(self): 37 | if not self.is_premium or not self.premium_expiry: 38 | return False 39 | return self.premium_expiry - timezone.now() <= timedelta(days=7) 40 | 41 | 42 | class MpesaTransaction(models.Model): 43 | transaction_id = models.CharField(max_length=100, unique=True) 44 | amount = models.DecimalField(max_digits=10, decimal_places=2) 45 | phone_number = models.CharField(max_length=15) 46 | transaction_date = models.DateTimeField() 47 | is_used = models.BooleanField(default=False) 48 | user = models.ForeignKey( 49 | settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True 50 | ) 51 | 52 | def __str__(self): 53 | return f"{self.transaction_id} - {self.amount}" 54 | -------------------------------------------------------------------------------- /groups/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Group, GroupPost, GroupMessage 3 | 4 | 5 | @admin.register(Group) 6 | class GroupAdmin(admin.ModelAdmin): 7 | <<<<<<< HEAD 8 | list_display = ("name", "description", "created_at") 9 | search_fields = ("name", "description") 10 | list_filter = ("created_at",) 11 | ordering = ("name",) 12 | 13 | 14 | @admin.register(GroupPost) 15 | class GroupPostAdmin(admin.ModelAdmin): 16 | list_display = ("group", "user", "content", "created_at", "n_views", "get_n_likes") 17 | search_fields = ("group__name", "user__username", "content") 18 | list_filter = ("created_at",) 19 | ordering = ("-created_at",) 20 | 21 | def get_n_likes(self, obj): 22 | return obj.n_likes 23 | 24 | get_n_likes.short_description = "Number of Likes" 25 | 26 | 27 | @admin.register(GroupMessage) 28 | class GroupMessageAdmin(admin.ModelAdmin): 29 | list_display = ("group", "sender", "content", "timestamp") 30 | search_fields = ("group__name", "sender__username", "content") 31 | list_filter = ("timestamp",) 32 | ordering = ("-timestamp",) 33 | ======= 34 | list_display = ('name', 'description', 'created_at') 35 | search_fields = ('name', 'description') 36 | list_filter = ('created_at',) 37 | ordering = ('name',) 38 | 39 | @admin.register(GroupPost) 40 | class GroupPostAdmin(admin.ModelAdmin): 41 | list_display = ('group', 'user', 'content', 'created_at', 'n_views', 'get_n_likes') 42 | search_fields = ('group__name', 'user__username', 'content') 43 | list_filter = ('created_at',) 44 | ordering = ('-created_at',) 45 | 46 | def get_n_likes(self, obj): 47 | return obj.n_likes 48 | get_n_likes.short_description = 'Number of Likes' 49 | 50 | @admin.register(GroupMessage) 51 | class GroupMessageAdmin(admin.ModelAdmin): 52 | list_display = ('group', 'sender', 'content', 'timestamp') 53 | search_fields = ('group__name', 'sender__username', 'content') 54 | list_filter = ('timestamp',) 55 | ordering = ('-timestamp',) 56 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 57 | -------------------------------------------------------------------------------- /groups/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import Group, GroupPost 3 | 4 | <<<<<<< HEAD 5 | 6 | class GroupForm(forms.ModelForm): 7 | class Meta: 8 | model = Group 9 | fields = ["name", "description", "visibility", "category", "tags"] 10 | widgets = { 11 | "name": forms.TextInput(attrs={"class": "form-control-dark"}), 12 | "description": forms.Textarea(attrs={"class": "form-control-dark"}), 13 | "visibility": forms.Select(attrs={"class": "form-control-dark"}), 14 | "category": forms.Select(attrs={"class": "form-control-dark"}), 15 | "tags": forms.TextInput( 16 | attrs={ 17 | "class": "form-control-dark", 18 | "placeholder": "Enter tags separated by commas", 19 | } 20 | ), 21 | } 22 | 23 | def __init__(self, *args, **kwargs): 24 | super().__init__(*args, **kwargs) 25 | for field in self.fields.values(): 26 | if "class" not in field.widget.attrs: 27 | field.widget.attrs["class"] = "form-control-dark" 28 | 29 | 30 | class GroupPostForm(forms.ModelForm): 31 | class Meta: 32 | model = GroupPost 33 | fields = ["content"] 34 | widgets = { 35 | "content": forms.Textarea(attrs={"class": "form-control-dark"}), 36 | } 37 | 38 | def __init__(self, *args, **kwargs): 39 | super().__init__(*args, **kwargs) 40 | for field in self.fields.values(): 41 | if "class" not in field.widget.attrs: 42 | field.widget.attrs["class"] = "form-control-dark" 43 | ======= 44 | class GroupForm(forms.ModelForm): 45 | class Meta: 46 | model = Group 47 | fields = ['name', 'description', 'visibility', 'category', 'tags'] 48 | widgets = { 49 | 'tags': forms.TextInput(attrs={'placeholder': 'Enter tags separated by commas'}), 50 | } 51 | 52 | class GroupPostForm(forms.ModelForm): 53 | class Meta: 54 | model = GroupPost 55 | fields = ['content'] 56 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JooustConnect 2 | 3 | JooustConnect is a Django-based application designed to facilitate social networking and communication within a community. The application includes features for managing groups, messaging, notifications, and user profiles. 4 | 5 | ## Features 6 | 7 | - **Groups**: Create and manage groups, view group details, and handle group requests. 8 | - **Messaging**: Send and receive messages within the platform. 9 | - **Notifications**: Receive and manage notifications related to user activities. 10 | - **User Profiles**: Manage user profiles, including profile pictures and premium features. 11 | 12 | ## Installation 13 | 14 | 1. **Clone the repository:** 15 | ```bash 16 | git clone https://github.com/phantom-kali/JooustConnect.git 17 | ``` 18 | 19 | 2. **Navigate to the project directory:** 20 | ```bash 21 | cd JooustConnect 22 | ``` 23 | 24 | 3. **Create a virtual environment and activate it:** 25 | ```bash 26 | python -m venv venv 27 | source venv/bin/activate # On Windows use `venv\Scripts\activate` 28 | ``` 29 | 30 | 4. **Install the requirements:** 31 | ```bash 32 | pip install -r requirements.txt 33 | ``` 34 | 35 | 5. **Apply migrations:** 36 | ```bash 37 | python manage.py migrate 38 | ``` 39 | 40 | 6. **Run the development server:** 41 | ```bash 42 | python manage.py runserver 43 | ``` 44 | 45 | ## Usage 46 | 47 | - Access the application at `http://127.0.0.1:8000/`. 48 | - Use the admin interface to manage groups, users, and notifications. 49 | 50 | ## Contributing 51 | 52 | 1. Fork the repository. 53 | 2. Create a feature branch: 54 | ```bash 55 | git checkout -b feature/YourFeature 56 | ``` 57 | 3. Commit your changes: 58 | ```bash 59 | git commit -am 'Add new feature' 60 | ``` 61 | 4. Push to the branch: 62 | ```bash 63 | git push origin feature/YourFeature 64 | ``` 65 | 5. Open a pull request. 66 | 67 | ## License 68 | 69 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 70 | 71 | ## Contact 72 | 73 | For any questions or feedback, please contact [fideleliudclimax@gmail.com](mailto:fideleliudclimax@gmail.com). 74 | -------------------------------------------------------------------------------- /users/templates/users/purchase_premium.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

{% if is_renewal %}Renew{% else %}Upgrade to{% endif %} Premium

6 | 7 |
8 | {% if is_renewal %} 9 |

Your current premium subscription will expire in {{ premium_days_left }} days.

10 | {% else %} 11 |

Enjoy exclusive benefits including:

12 |
    13 |
  • Verification Check
  • 14 |
  • Boosted posts
  • 15 |
  • Detailed analytics
  • 16 |
  • View who's seen your posts
  • 17 |
18 | {% endif %} 19 |
20 | 21 |

Premium subscription fee: Ksh 200
22 | Pay Exact amount To: 07-92-46-91-73

23 | 24 |
25 |
{% if is_renewal %}Renew{% else %}Activate{% endif %} Premium
26 |
27 | {% csrf_token %} 28 |
29 | 30 | 31 |
32 | 33 |
34 |
35 | 36 | {% if messages %} 37 |
38 |
    39 | {% for message in messages %} 40 |
  • {{ message }}
  • 41 | {% endfor %} 42 |
43 |
44 | {% endif %} 45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /users/templates/users/user_followers.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

{{ user.username }}'s Followers

6 |
7 | {% for follower in followers %} 8 |
9 |
10 | {% if follower.profile_picture %} 11 | {{ follower.username }} 13 | {% else %} 14 | {{ follower.username }} 16 | {% endif %} 17 |
18 | {{ follower.username }} 19 |
20 | {% if follower.is_verified %} 21 | 22 | {% endif %} 23 |
24 | {% if request.user != follower %} 25 | {% if follower in request.user.following.all %} 26 |
27 | {% csrf_token %} 28 | 29 |
30 | {% else %} 31 |
32 | {% csrf_token %} 33 | 34 |
35 | {% endif %} 36 | {% endif %} 37 |
38 | {% empty %} 39 |
No followers
40 | {% endfor %} 41 |
42 |
43 | {% endblock %} -------------------------------------------------------------------------------- /notifications/models.py: -------------------------------------------------------------------------------- 1 | # from django.db import models 2 | from django.urls import reverse 3 | <<<<<<< HEAD 4 | 5 | ======= 6 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 7 | # from users.models import User 8 | 9 | from django.contrib.auth import get_user_model 10 | from django.db import models 11 | 12 | User = get_user_model() 13 | 14 | <<<<<<< HEAD 15 | 16 | class Notification(models.Model): 17 | USER_CHOICES = ( 18 | ("DM", "Direct Message"), 19 | ("GROUP", "Group Message"), 20 | ) 21 | user = models.ForeignKey( 22 | User, on_delete=models.CASCADE, related_name="notifications" 23 | ) 24 | content = models.CharField(max_length=255) 25 | is_read = models.BooleanField(default=False) 26 | timestamp = models.DateTimeField(auto_now_add=True) 27 | notification_type = models.CharField( 28 | max_length=5, choices=USER_CHOICES, default="DM" 29 | ) 30 | related_id = models.IntegerField() # This will store the DM or Group ID 31 | 32 | def get_redirect_url(self): 33 | if self.notification_type == "DM": 34 | return f"/messaging/{self.related_id}/" 35 | elif self.notification_type == "GROUP": 36 | return f"/groups/{self.related_id}/" 37 | return reverse("notifications") # Default redirect 38 | ======= 39 | class Notification(models.Model): 40 | USER_CHOICES = ( 41 | ('DM', 'Direct Message'), 42 | ('GROUP', 'Group Message'), 43 | ) 44 | user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='notifications') 45 | content = models.CharField(max_length=255) 46 | is_read = models.BooleanField(default=False) 47 | timestamp = models.DateTimeField(auto_now_add=True) 48 | notification_type = models.CharField(max_length=5, choices=USER_CHOICES, default='DM') 49 | related_id = models.IntegerField() # This will store the DM or Group ID 50 | 51 | def get_redirect_url(self): 52 | if self.notification_type == 'DM': 53 | return f'/messaging/{self.related_id}/' 54 | elif self.notification_type == 'GROUP': 55 | return f'/groups/{self.related_id}/' 56 | return reverse('notifications') # Default redirect 57 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 58 | -------------------------------------------------------------------------------- /users/forms.py: -------------------------------------------------------------------------------- 1 | from django import forms 2 | from .models import User 3 | 4 | 5 | class UserRegistrationForm(forms.ModelForm): 6 | password = forms.CharField(widget=forms.PasswordInput) 7 | confirm_password = forms.CharField(widget=forms.PasswordInput) 8 | course = forms.CharField(max_length=100, required=False) 9 | 10 | class Meta: 11 | model = User 12 | fields = [ 13 | "username", 14 | "email", 15 | "nickname", 16 | "course", 17 | "year", 18 | "profile_picture", 19 | "bio", 20 | "password", 21 | ] 22 | 23 | def __init__(self, *args, **kwargs): 24 | super().__init__(*args, **kwargs) 25 | self.fields["course"].required = True 26 | self.fields["year"].required = True 27 | 28 | # Suppress help texts 29 | for field in self.fields.values(): 30 | field.help_text = "" 31 | 32 | # Set bio field to use TextInput widget 33 | self.fields["bio"].widget = forms.TextInput(attrs={"placeholder": "Your about"}) 34 | 35 | def clean(self): 36 | cleaned_data = super().clean() 37 | password = cleaned_data.get("password") 38 | confirm_password = cleaned_data.get("confirm_password") 39 | if password != confirm_password: 40 | raise forms.ValidationError("Passwords do not match") 41 | return cleaned_data 42 | 43 | 44 | class UserProfileForm(forms.ModelForm): 45 | class Meta: 46 | model = User 47 | fields = ["nickname", "course", "year", "bio", "profile_picture"] 48 | widgets = { 49 | "nickname": forms.TextInput(attrs={"class": "form-control"}), 50 | "course": forms.TextInput(attrs={"class": "form-control"}), 51 | "year": forms.TextInput(attrs={"class": "form-control"}), 52 | "bio": forms.Textarea(attrs={"class": "form-control"}), 53 | } 54 | 55 | 56 | class UserSettingsForm(forms.ModelForm): 57 | class Meta: 58 | model = User 59 | fields = ["theme_preference", "privacy_dms", "privacy_posts"] 60 | widgets = { 61 | "theme_preference": forms.RadioSelect(), 62 | "privacy_dms": forms.CheckboxInput(), 63 | "privacy_posts": forms.CheckboxInput(), 64 | } 65 | -------------------------------------------------------------------------------- /users/templates/users/profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |
6 | 7 |
8 | {% if user.profile_picture %} 9 | {{ user.username }} 11 | {% else %} 12 | {{ user.username }} 14 | {% endif %} 15 |
16 | 17 |
18 |

{{ user.username }} 19 | {% if user.is_verified %} 20 | Verified 21 | {% endif %} 22 |

23 |

{{ user.bio }}

24 |

Course: {{ user.course }}

25 |

Year: {{ user.year }}

26 |

27 | Followers: {{ followers_count }} | 28 | Following: {{ following_count }} 29 |

30 | 31 | 32 |
33 | {% if request.user == user %} 34 | Edit Profile 35 | {% elif request.user in user.followers.all %} 36 |
37 | {% csrf_token %} 38 | 39 |
40 | {% else %} 41 |
42 | {% csrf_token %} 43 | 44 |
45 | {% endif %} 46 |
47 |
48 |
49 |
50 | {% endblock %} -------------------------------------------------------------------------------- /groups/migrations/0002_group_category_group_tags_group_visibility_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-03 16:47 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 | ("groups", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name="group", 18 | name="category", 19 | field=models.CharField(blank=True, max_length=50), 20 | ), 21 | migrations.AddField( 22 | model_name="group", 23 | name="tags", 24 | field=models.CharField(blank=True, max_length=200), 25 | ), 26 | migrations.AddField( 27 | model_name="group", 28 | name="visibility", 29 | field=models.CharField( 30 | choices=[ 31 | ("public", "Public"), 32 | ("private", "Private"), 33 | ("secret", "Secret"), 34 | ], 35 | default="public", 36 | max_length=10, 37 | ), 38 | ), 39 | migrations.CreateModel( 40 | name="GroupJoinRequest", 41 | fields=[ 42 | ( 43 | "id", 44 | models.BigAutoField( 45 | auto_created=True, 46 | primary_key=True, 47 | serialize=False, 48 | verbose_name="ID", 49 | ), 50 | ), 51 | ("created_at", models.DateTimeField(auto_now_add=True)), 52 | ( 53 | "group", 54 | models.ForeignKey( 55 | on_delete=django.db.models.deletion.CASCADE, 56 | related_name="join_requests", 57 | to="groups.group", 58 | ), 59 | ), 60 | ( 61 | "user", 62 | models.ForeignKey( 63 | on_delete=django.db.models.deletion.CASCADE, 64 | to=settings.AUTH_USER_MODEL, 65 | ), 66 | ), 67 | ], 68 | options={ 69 | "unique_together": {("group", "user")}, 70 | }, 71 | ), 72 | ] 73 | -------------------------------------------------------------------------------- /messaging/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 11:45 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 | <<<<<<< HEAD 19 | name="Message", 20 | fields=[ 21 | ( 22 | "id", 23 | models.BigAutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("content", models.TextField()), 31 | ("timestamp", models.DateTimeField(auto_now_add=True)), 32 | ( 33 | "receiver", 34 | models.ForeignKey( 35 | on_delete=django.db.models.deletion.CASCADE, 36 | related_name="received_messages", 37 | to=settings.AUTH_USER_MODEL, 38 | ), 39 | ), 40 | ( 41 | "sender", 42 | models.ForeignKey( 43 | on_delete=django.db.models.deletion.CASCADE, 44 | related_name="sent_messages", 45 | to=settings.AUTH_USER_MODEL, 46 | ), 47 | ), 48 | ], 49 | options={ 50 | "ordering": ["timestamp"], 51 | ======= 52 | name='Message', 53 | fields=[ 54 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 55 | ('content', models.TextField()), 56 | ('timestamp', models.DateTimeField(auto_now_add=True)), 57 | ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)), 58 | ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), 59 | ], 60 | options={ 61 | 'ordering': ['timestamp'], 62 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 63 | }, 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /groups/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | <<<<<<< HEAD 6 | path("", views.groups, name="groups"), 7 | path("create/", views.create_group, name="create_group"), 8 | path("/", views.group_detail, name="group_detail"), 9 | path("/join/", views.join_group, name="join_group"), 10 | path("/leave/", views.leave_group, name="leave_group"), 11 | path( 12 | "/manage-requests/", 13 | views.manage_group_requests, 14 | name="manage_group_requests", 15 | ), 16 | path("/post/", views.add_group_post, name="add_group_post"), 17 | path("/messages/", views.group_messages, name="group_messages"), 18 | path( 19 | "poll-group-messages//", 20 | views.poll_group_messages, 21 | name="poll_group_messages", 22 | ), 23 | path("send-group-message/", views.send_group_message, name="send_group_message"), 24 | path("/delete/", views.delete_group, name="delete_group"), 25 | path( 26 | "/remove-member//", 27 | views.remove_member, 28 | name="remove_member", 29 | ), 30 | path("/invite/", views.invite_to_group, name="invite_to_group"), 31 | path( 32 | "redirect-to-group//", 33 | views.redirect_to_group, 34 | name="redirect_to_group", 35 | ), 36 | ] 37 | ======= 38 | path('', views.groups, name='groups'), 39 | path('create/', views.create_group, name='create_group'), 40 | path('/', views.group_detail, name='group_detail'), 41 | path('/join/', views.join_group, name='join_group'), 42 | path('/leave/', views.leave_group, name='leave_group'), 43 | path('/manage-requests/', views.manage_group_requests, name='manage_group_requests'), 44 | path('/post/', views.add_group_post, name='add_group_post'), 45 | path('/messages/', views.group_messages, name='group_messages'), 46 | path('poll-group-messages//', views.poll_group_messages, name='poll_group_messages'), 47 | path('send-group-message/', views.send_group_message, name='send_group_message'), 48 | 49 | path('/delete/', views.delete_group, name='delete_group'), 50 | path('/remove-member//', views.remove_member, name='remove_member'), 51 | path('/invite/', views.invite_to_group, name='invite_to_group'), 52 | 53 | path('redirect-to-group//', views.redirect_to_group, name='redirect_to_group'), 54 | ] 55 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 56 | -------------------------------------------------------------------------------- /users/utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from datetime import datetime 3 | 4 | <<<<<<< HEAD 5 | 6 | def extract_mpesa_details(message, action): 7 | if action == "add": 8 | transaction_id_match = re.search(r"([A-Z0-9]+) Confirmed", message) 9 | amount_match = re.search(r"Ksh([\d,]+\.\d{2})", message) 10 | date_match = re.search( 11 | r"on (\d{1,2}/\d{1,2}/\d{2}) at (\d{1,2}:\d{2} [APM]{2})", message 12 | ) 13 | phone_match = re.search(r"from .+?\b(\d{10})\b", message) 14 | elif action == "verify": 15 | transaction_id_match = re.search(r"([A-Z0-9]+) Confirmed", message) 16 | amount_match = re.search(r"Ksh([\d,]+\.\d{2})", message) 17 | date_match = re.search( 18 | r"on (\d{1,2}/\d{1,2}/\d{2}) at (\d{1,2}:\d{2} [APM]{2})", message 19 | ) 20 | phone_match = re.search(r"to .+?\b(\d{10})\b", message) 21 | 22 | if transaction_id_match and amount_match and date_match and phone_match: 23 | transaction_id = transaction_id_match.group(1) 24 | amount = float(amount_match.group(1).replace(",", "")) 25 | date_str = f"{date_match.group(1)} {date_match.group(2)}" 26 | transaction_date = datetime.strptime(date_str, "%d/%m/%y %I:%M %p") 27 | phone_number = phone_match.group(1) 28 | 29 | return transaction_id, amount, transaction_date, phone_number 30 | 31 | return None, None, None, None 32 | ======= 33 | def extract_mpesa_details(message, action): 34 | if action == 'add': 35 | transaction_id_match = re.search(r'([A-Z0-9]+) Confirmed', message) 36 | amount_match = re.search(r'Ksh([\d,]+\.\d{2})', message) 37 | date_match = re.search(r'on (\d{1,2}/\d{1,2}/\d{2}) at (\d{1,2}:\d{2} [APM]{2})', message) 38 | phone_match = re.search(r'from .+?\b(\d{10})\b', message) 39 | elif action == 'verify': 40 | transaction_id_match = re.search(r'([A-Z0-9]+) Confirmed', message) 41 | amount_match = re.search(r'Ksh([\d,]+\.\d{2})', message) 42 | date_match = re.search(r'on (\d{1,2}/\d{1,2}/\d{2}) at (\d{1,2}:\d{2} [APM]{2})', message) 43 | phone_match = re.search(r'to .+?\b(\d{10})\b', message) 44 | 45 | if transaction_id_match and amount_match and date_match and phone_match: 46 | transaction_id = transaction_id_match.group(1) 47 | amount = float(amount_match.group(1).replace(',', '')) 48 | date_str = f"{date_match.group(1)} {date_match.group(2)}" 49 | transaction_date = datetime.strptime(date_str, "%d/%m/%y %I:%M %p") 50 | phone_number = phone_match.group(1) 51 | 52 | return transaction_id, amount, transaction_date, phone_number 53 | 54 | return None, None, None, None 55 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 56 | -------------------------------------------------------------------------------- /users/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from django.contrib.auth.views import LoginView, LogoutView 3 | from . import views 4 | 5 | urlpatterns = [ 6 | <<<<<<< HEAD 7 | path("register/", views.register, name="register"), 8 | path( 9 | "login/", 10 | LoginView.as_view( 11 | template_name="users/login.html", redirect_authenticated_user=True 12 | ), 13 | name="login", 14 | ), 15 | path("logout/", LogoutView.as_view(next_page="login"), name="logout"), 16 | path("settings/", views.settings, name="settings"), 17 | path("search-users/", views.search_users, name="search_users"), 18 | path("premium-dashboard/", views.premium_dashboard, name="premium_dashboard"), 19 | path("purchase-premium/", views.purchase_premium, name="purchase_premium"), 20 | path( 21 | "add-mpesa-transaction/", 22 | views.add_mpesa_transaction, 23 | name="add_mpesa_transaction", 24 | ), 25 | path("profile//", views.profile, name="profile"), 26 | path("profile//edit/", views.edit_profile, name="edit_profile"), 27 | path("follow//", views.follow_user, name="follow_user"), 28 | path("unfollow//", views.unfollow_user, name="unfollow_user"), 29 | path( 30 | "profile//followers/", views.user_followers, name="user_followers" 31 | ), 32 | path( 33 | "profile//following/", views.user_following, name="user_following" 34 | ), 35 | ] 36 | ======= 37 | path('register/', views.register, name='register'), 38 | path('login/', LoginView.as_view(template_name='users/login.html', redirect_authenticated_user=True), name='login'), 39 | path('logout/', LogoutView.as_view(next_page='login'), name='logout'), 40 | path('settings/', views.settings, name='settings'), 41 | path('search-users/', views.search_users, name='search_users'), 42 | path('premium-dashboard/', views.premium_dashboard, name='premium_dashboard'), 43 | path('purchase-premium/', views.purchase_premium, name='purchase_premium'), 44 | path('add-mpesa-transaction/', views.add_mpesa_transaction, name='add_mpesa_transaction'), 45 | 46 | path('profile//', views.profile, name='profile'), 47 | path('profile//edit/', views.edit_profile, name='edit_profile'), 48 | path('follow//', views.follow_user, name='follow_user'), 49 | path('unfollow//', views.unfollow_user, name='unfollow_user'), 50 | 51 | path('profile//followers/', views.user_followers, name='user_followers'), 52 | path('profile//following/', views.user_following, name='user_following'), 53 | 54 | ] 55 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 56 | -------------------------------------------------------------------------------- /social/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Post, PostView, Comment, Report 3 | 4 | 5 | @admin.register(Post) 6 | class PostAdmin(admin.ModelAdmin): 7 | <<<<<<< HEAD 8 | list_display = ("user", "content", "created_at", "n_views", "n_likes") 9 | search_fields = ("user__username", "content") 10 | list_filter = ("created_at",) 11 | ordering = ("-created_at",) 12 | 13 | 14 | @admin.register(PostView) 15 | class PostViewAdmin(admin.ModelAdmin): 16 | list_display = ("post", "user", "viewed_at") 17 | search_fields = ("post__content", "user__username") 18 | list_filter = ("viewed_at",) 19 | ordering = ("-viewed_at",) 20 | 21 | 22 | @admin.register(Report) 23 | class ReportAdmin(admin.ModelAdmin): 24 | list_display = ("reporter", "post", "report_type", "created_at", "is_resolved") 25 | list_filter = ("report_type", "is_resolved", "created_at") 26 | search_fields = ("reporter__username", "post__content", "description") 27 | actions = ["mark_resolved"] 28 | 29 | def mark_resolved(self, request, queryset): 30 | queryset.update(is_resolved=True) 31 | 32 | mark_resolved.short_description = "Mark selected reports as resolved" 33 | 34 | 35 | @admin.register(Comment) 36 | class CommentAdmin(admin.ModelAdmin): 37 | list_display = ("post", "user", "content", "created_at") 38 | search_fields = ("post__content", "user__username", "content") 39 | list_filter = ("created_at",) 40 | ordering = ("-created_at",) 41 | ======= 42 | list_display = ('user', 'content', 'created_at', 'n_views', 'n_likes') 43 | search_fields = ('user__username', 'content') 44 | list_filter = ('created_at',) 45 | ordering = ('-created_at',) 46 | 47 | @admin.register(PostView) 48 | class PostViewAdmin(admin.ModelAdmin): 49 | list_display = ('post', 'user', 'viewed_at') 50 | search_fields = ('post__content', 'user__username') 51 | list_filter = ('viewed_at',) 52 | ordering = ('-viewed_at',) 53 | 54 | @admin.register(Report) 55 | class ReportAdmin(admin.ModelAdmin): 56 | list_display = ('reporter', 'post', 'report_type', 'created_at', 'is_resolved') 57 | list_filter = ('report_type', 'is_resolved', 'created_at') 58 | search_fields = ('reporter__username', 'post__content', 'description') 59 | actions = ['mark_resolved'] 60 | 61 | def mark_resolved(self, request, queryset): 62 | queryset.update(is_resolved=True) 63 | mark_resolved.short_description = "Mark selected reports as resolved" 64 | 65 | @admin.register(Comment) 66 | class CommentAdmin(admin.ModelAdmin): 67 | list_display = ('post', 'user', 'content', 'created_at') 68 | search_fields = ('post__content', 'user__username', 'content') 69 | list_filter = ('created_at',) 70 | ordering = ('-created_at',) 71 | 72 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 73 | -------------------------------------------------------------------------------- /users/templates/users/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |
7 |
8 |
9 |
10 |

Settings

11 |
12 |
13 |
14 | {% csrf_token %} 15 | 16 |

Theme Preference

17 |
18 | {% for radio in form.theme_preference %} 19 |
20 | {{ radio.tag }} 21 | 24 |
25 | {% endfor %} 26 |
27 | 28 |

Privacy Settings

29 |
30 | {{ form.privacy_dms }} 31 | 34 |
35 |
36 | {{ form.privacy_posts }} 37 | 40 |
41 | 42 | 43 |
44 |
45 |
46 |
47 |
48 |
49 | {% endblock %} 50 | 51 | {% if messages %} 52 |
53 | {% for message in messages %} 54 |
55 | {{ message }} 56 |
57 | {% endfor %} 58 |
59 | {% endif %} 60 | 61 | {% block extra_js %} 62 | 70 | {% endblock %} -------------------------------------------------------------------------------- /notifications/views.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from django.contrib.auth.decorators import login_required 3 | from .models import Notification 4 | from django.shortcuts import get_object_or_404, render 5 | 6 | <<<<<<< HEAD 7 | 8 | @login_required 9 | def notifications(request): 10 | notifications = Notification.objects.filter(user=request.user).order_by( 11 | "-timestamp" 12 | ) 13 | return render( 14 | request, "notifications/notifications.html", {"notifications": notifications} 15 | ) 16 | ======= 17 | @login_required 18 | def notifications(request): 19 | notifications = Notification.objects.filter(user=request.user).order_by('-timestamp') 20 | return render(request, 'notifications/notifications.html', {'notifications': notifications}) 21 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 22 | 23 | 24 | @login_required 25 | def mark_notification_read(request, notification_id): 26 | <<<<<<< HEAD 27 | notification = get_object_or_404( 28 | Notification, id=notification_id, user=request.user 29 | ) 30 | notification.is_read = True 31 | notification.save() 32 | return JsonResponse({"status": "success"}) 33 | 34 | 35 | @login_required 36 | def mark_all_notifications_read(request): 37 | if request.method == "POST": 38 | Notification.objects.filter(user=request.user, is_read=False).update( 39 | is_read=True 40 | ) 41 | return JsonResponse({"status": "success"}) 42 | return JsonResponse({"status": "error"}, status=405) 43 | 44 | 45 | @login_required 46 | def poll_notifications(request): 47 | last_id = request.GET.get("last_id", 0) 48 | notifications = Notification.objects.filter( 49 | user=request.user, id__gt=last_id 50 | ).order_by("id") 51 | unread_count = Notification.objects.filter(user=request.user, is_read=False).count() 52 | 53 | return JsonResponse( 54 | { 55 | "notifications": [ 56 | { 57 | "id": n.id, 58 | "content": n.content, 59 | "timestamp": n.timestamp.strftime("%Y-%m-%d %H:%M:%S"), 60 | "redirect_url": n.get_redirect_url(), 61 | } 62 | for n in notifications 63 | ], 64 | "unread_count": unread_count, 65 | } 66 | ) 67 | ======= 68 | notification = get_object_or_404(Notification, id=notification_id, user=request.user) 69 | notification.is_read = True 70 | notification.save() 71 | return JsonResponse({'status': 'success'}) 72 | 73 | @login_required 74 | def mark_all_notifications_read(request): 75 | if request.method == 'POST': 76 | Notification.objects.filter(user=request.user, is_read=False).update(is_read=True) 77 | return JsonResponse({'status': 'success'}) 78 | return JsonResponse({'status': 'error'}, status=405) 79 | 80 | @login_required 81 | def poll_notifications(request): 82 | last_id = request.GET.get('last_id', 0) 83 | notifications = Notification.objects.filter(user=request.user, id__gt=last_id).order_by('id') 84 | unread_count = Notification.objects.filter(user=request.user, is_read=False).count() 85 | 86 | return JsonResponse({ 87 | 'notifications': [ 88 | { 89 | 'id': n.id, 90 | 'content': n.content, 91 | 'timestamp': n.timestamp.strftime("%Y-%m-%d %H:%M:%S"), 92 | 'redirect_url': n.get_redirect_url(), 93 | } for n in notifications 94 | ], 95 | 'unread_count': unread_count 96 | }) 97 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 98 | -------------------------------------------------------------------------------- /users/templates/users/edit_profile.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block content %} 4 |
5 |
6 |
7 |
8 |
9 |

Edit Profile

10 |
11 |
12 |
13 | {% csrf_token %} 14 |
15 |
16 |
17 | Profile Picture 22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | {{ form.nickname.label_tag }} 30 | {{ form.nickname }} 31 |
32 |
33 | {{ form.course.label_tag }} 34 | {{ form.course }} 35 |
36 |
37 | {{ form.year.label_tag }} 38 | {{ form.year }} 39 |
40 |
41 | {{ form.bio.label_tag }} 42 | {{ form.bio }} 43 |
44 |
45 | {{ form.privacy_dms }} 46 | {{ form.privacy_dms.label_tag }} 47 |
48 |
49 | {{ form.privacy_posts }} 50 | {{ form.privacy_posts.label_tag }} 51 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /groups/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from users.models import User 3 | 4 | <<<<<<< HEAD 5 | 6 | class Group(models.Model): 7 | VISIBILITY_CHOICES = [ 8 | ("public", "Public"), 9 | ("private", "Private"), 10 | ("secret", "Secret"), 11 | ] 12 | name = models.CharField(max_length=100, unique=True) 13 | description = models.TextField(blank=True) 14 | members = models.ManyToManyField(User, related_name="joined_groups") 15 | created_at = models.DateTimeField(auto_now_add=True) 16 | admins = models.ManyToManyField(User, related_name="admin_groups", blank=True) 17 | visibility = models.CharField( 18 | max_length=10, choices=VISIBILITY_CHOICES, default="public" 19 | ) 20 | category = models.CharField(max_length=50, blank=True) 21 | tags = models.CharField( 22 | max_length=200, blank=True 23 | ) # Store tags as comma-separated values 24 | 25 | def __str__(self): 26 | return self.name 27 | 28 | 29 | class GroupJoinRequest(models.Model): 30 | group = models.ForeignKey( 31 | Group, on_delete=models.CASCADE, related_name="join_requests" 32 | ) 33 | user = models.ForeignKey(User, on_delete=models.CASCADE) 34 | created_at = models.DateTimeField(auto_now_add=True) 35 | 36 | class Meta: 37 | unique_together = ("group", "user") 38 | 39 | 40 | class GroupPost(models.Model): 41 | group = models.ForeignKey(Group, related_name="posts", on_delete=models.CASCADE) 42 | ======= 43 | class Group(models.Model): 44 | VISIBILITY_CHOICES = [ 45 | ('public', 'Public'), 46 | ('private', 'Private'), 47 | ('secret', 'Secret'), 48 | ] 49 | name = models.CharField(max_length=100, unique=True) 50 | description = models.TextField(blank=True) 51 | members = models.ManyToManyField(User, related_name='joined_groups') 52 | created_at = models.DateTimeField(auto_now_add=True) 53 | admins = models.ManyToManyField(User, related_name='admin_groups', blank=True) 54 | visibility = models.CharField(max_length=10, choices=VISIBILITY_CHOICES, default='public') 55 | category = models.CharField(max_length=50, blank=True) 56 | tags = models.CharField(max_length=200, blank=True) # Store tags as comma-separated values 57 | 58 | def __str__(self): 59 | return self.name 60 | 61 | class GroupJoinRequest(models.Model): 62 | group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='join_requests') 63 | user = models.ForeignKey(User, on_delete=models.CASCADE) 64 | created_at = models.DateTimeField(auto_now_add=True) 65 | 66 | class Meta: 67 | unique_together = ('group', 'user') 68 | 69 | class GroupPost(models.Model): 70 | group = models.ForeignKey(Group, related_name='posts', on_delete=models.CASCADE) 71 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 72 | user = models.ForeignKey(User, on_delete=models.CASCADE) 73 | content = models.TextField() 74 | created_at = models.DateTimeField(auto_now_add=True) 75 | n_views = models.PositiveIntegerField(default=0) 76 | <<<<<<< HEAD 77 | likes = models.ManyToManyField(User, related_name="liked_group_posts", blank=True) 78 | 79 | 80 | class GroupMessage(models.Model): 81 | group = models.ForeignKey(Group, related_name="messages", on_delete=models.CASCADE) 82 | sender = models.ForeignKey( 83 | User, related_name="sent_group_messages", on_delete=models.CASCADE 84 | ) 85 | ======= 86 | likes = models.ManyToManyField(User, related_name='liked_group_posts', blank=True) 87 | 88 | class GroupMessage(models.Model): 89 | group = models.ForeignKey(Group, related_name='messages', on_delete=models.CASCADE) 90 | sender = models.ForeignKey(User, related_name='sent_group_messages', on_delete=models.CASCADE) 91 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 92 | content = models.TextField() 93 | timestamp = models.DateTimeField(auto_now_add=True) 94 | -------------------------------------------------------------------------------- /groups/templates/groups/create_group.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | <<<<<<< HEAD 3 | {% load widget_tweaks %} 4 | {% load static %} 5 | 6 | {% block title %}Create New Group - JouustConnect{% endblock %} 7 | 8 | {% block extra_css %} 9 | 10 | 17 | {% endblock %} 18 | 19 | {% block content %} 20 |
21 |
22 |
23 |
24 |
25 |

Create New Group

26 |
27 |
28 |
29 | {% csrf_token %} 30 | {% for field in form %} 31 |
32 | 33 | {{ field }} 34 | {% if field.errors %} 35 |
{{ field.errors|striptags }}
36 | {% endif %} 37 |
38 | {% endfor %} 39 |
40 | 41 |
42 |
43 |
44 |
45 | ======= 46 | {% load static %} 47 | 48 | {% block content %} 49 |
50 |
51 |
52 |

Create New Group

53 |
54 | {% csrf_token %} 55 | {% for field in form %} 56 |
57 | 60 | {{ field }} 61 | {% if field.errors %} 62 |

{{ field.errors|striptags }}

63 | {% endif %} 64 |
65 | {% endfor %} 66 |
67 | 70 |
71 |
72 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 73 |
74 |
75 |
76 | {% endblock %} 77 | 78 | <<<<<<< HEAD 79 | {% block extra_js %} 80 | 81 | ======= 82 | {% block extra_css %} 83 | 84 | 98 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 99 | {% endblock %} -------------------------------------------------------------------------------- /social/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from users.models import User 4 | 5 | <<<<<<< HEAD 6 | 7 | class Post(models.Model): 8 | user = models.ForeignKey(User, on_delete=models.CASCADE) 9 | content = models.TextField(max_length=150) 10 | image = models.ImageField(upload_to="post_images/", blank=True, null=True) 11 | video = models.FileField(upload_to="post_videos/", blank=True, null=True) 12 | created_at = models.DateTimeField(auto_now_add=True) 13 | n_views = models.PositiveIntegerField(default=0) 14 | likes = models.ManyToManyField(User, related_name="liked_posts", blank=True) 15 | ======= 16 | class Post(models.Model): 17 | user = models.ForeignKey(User, on_delete=models.CASCADE) 18 | content = models.TextField(max_length=150) 19 | image = models.ImageField(upload_to='post_images/', blank=True, null=True) 20 | video = models.FileField(upload_to='post_videos/', blank=True, null=True) 21 | created_at = models.DateTimeField(auto_now_add=True) 22 | n_views = models.PositiveIntegerField(default=0) 23 | likes = models.ManyToManyField(User, related_name='liked_posts', blank=True) 24 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 25 | 26 | is_boosted = models.BooleanField(default=False) 27 | boost_expires_at = models.DateTimeField(null=True, blank=True) 28 | 29 | @property 30 | def n_likes(self): 31 | return self.likes.count() 32 | 33 | @property 34 | def n_comments(self): 35 | return self.comments.count() 36 | 37 | <<<<<<< HEAD 38 | 39 | class PostView(models.Model): 40 | post = models.ForeignKey(Post, related_name="views", on_delete=models.CASCADE) 41 | ======= 42 | class PostView(models.Model): 43 | post = models.ForeignKey(Post, related_name='views', on_delete=models.CASCADE) 44 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 45 | user = models.ForeignKey(User, on_delete=models.CASCADE) 46 | viewed_at = models.DateTimeField(auto_now_add=True) 47 | 48 | class Meta: 49 | <<<<<<< HEAD 50 | unique_together = ("post", "user") 51 | 52 | 53 | class Report(models.Model): 54 | REPORT_TYPES = ( 55 | ("spam", "Spam"), 56 | ("inappropriate", "Inappropriate Content"), 57 | ("harassment", "Harassment"), 58 | ("other", "Other"), 59 | ) 60 | 61 | reporter = models.ForeignKey( 62 | settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="reports_made" 63 | ) 64 | post = models.ForeignKey("Post", on_delete=models.CASCADE, related_name="reports") 65 | ======= 66 | unique_together = ('post', 'user') 67 | 68 | class Report(models.Model): 69 | REPORT_TYPES = ( 70 | ('spam', 'Spam'), 71 | ('inappropriate', 'Inappropriate Content'), 72 | ('harassment', 'Harassment'), 73 | ('other', 'Other'), 74 | ) 75 | 76 | reporter = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='reports_made') 77 | post = models.ForeignKey('Post', on_delete=models.CASCADE, related_name='reports') 78 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 79 | report_type = models.CharField(max_length=20, choices=REPORT_TYPES) 80 | description = models.TextField(blank=True) 81 | created_at = models.DateTimeField(auto_now_add=True) 82 | is_resolved = models.BooleanField(default=False) 83 | 84 | def __str__(self): 85 | return f"Report by {self.reporter.username} on post {self.post.id}" 86 | 87 | <<<<<<< HEAD 88 | 89 | class Comment(models.Model): 90 | post = models.ForeignKey(Post, related_name="comments", on_delete=models.CASCADE) 91 | user = models.ForeignKey(User, on_delete=models.CASCADE) 92 | content = models.TextField() 93 | created_at = models.DateTimeField(auto_now_add=True) 94 | ======= 95 | class Comment(models.Model): 96 | post = models.ForeignKey(Post, related_name='comments', on_delete=models.CASCADE) 97 | user = models.ForeignKey(User, on_delete=models.CASCADE) 98 | content = models.TextField() 99 | created_at = models.DateTimeField(auto_now_add=True) 100 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 101 | -------------------------------------------------------------------------------- /social/templates/social/post_create.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Create Post

6 |
7 |
8 |
9 | {% csrf_token %} 10 | {{ form.non_field_errors }} 11 | 12 |
13 | {{ form.content.errors }} 14 | {{ form.content }} 15 |
16 | 17 |
18 | 19 |
20 | 21 |
22 | Image preview 23 | 24 |
25 |
26 |
27 | 28 |
29 | 30 |
31 | 32 |
33 | 36 | 37 |
38 |
39 |
40 | 41 |
42 | 43 |
44 |
45 |
46 |
47 |
48 | 49 | {% block extra_js %} 50 | 93 | {% endblock %} 94 | {% endblock %} 95 | -------------------------------------------------------------------------------- /groups/templates/groups/manage_requests.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block title %}Manage {{ group.name }} - JouustConnect{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

Manage {{ group.name }}

10 | 11 |

Join Requests

12 | {% if join_requests %} 13 |
    14 | {% for request in join_requests %} 15 |
  • 16 | {{ request.user.username }} - Requested: {{ request.created_at|date:"F d, Y" }} 17 |
    18 |
    19 | {% csrf_token %} 20 | 21 | 22 | 23 |
    24 |
    25 |
  • 26 | {% endfor %} 27 |
28 | {% else %} 29 |

No pending join requests.

30 | {% endif %} 31 | 32 |

Manage Members

33 |
    34 | {% for member in members %} 35 |
  • 36 | {{ member.username }} 37 | {% if member in admins %} 38 | Admin 39 | {% endif %} 40 | 41 |
    42 | {% if member != admins.first %} 43 | {% if member in admins %} 44 |
    45 | {% csrf_token %} 46 | 47 | 48 |
    49 | {% else %} 50 |
    51 | {% csrf_token %} 52 | 53 | 54 |
    55 | {% endif %} 56 |
    57 | {% csrf_token %} 58 | 59 |
    60 | {% endif %} 61 |
    62 |
  • 63 | {% endfor %} 64 |
65 | 66 | Back to Group 67 |
68 |
69 |
70 | {% endblock %} -------------------------------------------------------------------------------- /notifications/templates/notifications/notifications.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block content %} 4 |
5 |

Notifications

6 |
7 | 8 | 9 |

10 | Unread Notifications 11 | 12 |

13 |
14 | {% for notification in notifications %} 15 | {% if not notification.is_read %} 16 | 25 | {% endif %} 26 | {% empty %} 27 |
No unread notifications
28 | {% endfor %} 29 |
30 | 31 | 32 |

Read Notifications

33 |
34 | {% for notification in notifications %} 35 | {% if notification.is_read %} 36 | 45 | {% endif %} 46 | {% empty %} 47 |
No read notifications
48 | {% endfor %} 49 |
50 |
51 | 52 | 107 | {% endblock %} -------------------------------------------------------------------------------- /users/templates/users/register.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% load widget_tweaks %} 4 | 5 | {% block content %} 6 |
7 |
8 |

Register

9 |
10 | {% csrf_token %} 11 | 12 | 13 |
14 | 15 | {% render_field form.username class="form-control" %} 16 |
17 | 18 | 19 |
20 | 21 | {% render_field form.email class="form-control" %} 22 |
23 | 24 | 25 |
26 | 27 | {% render_field form.password class="form-control" %} 28 |
29 | 30 | 31 |
32 | 33 | {% render_field form.confirm_password class="form-control" %} 34 |
35 | 36 | 37 |
38 |
Contains a number
39 |
Contains a lowercase letter
40 |
Contains an uppercase letter
41 |
Contains a special character
42 |
At least 8 characters long
43 |
44 |
45 | 46 | 47 |
48 | 49 | {% render_field form.course class="form-control" %} 50 |
51 | 52 | 53 |
54 | 55 | {% render_field form.year class="form-control" %} 56 |
57 | 58 | 59 |
60 | 61 | {% render_field form.profile_picture class="form-control" %} 62 | 64 |
65 | 66 | 67 |
68 | 69 | {% render_field form.bio class="form-control" %} 70 |
71 | 72 | 73 |
74 |
75 |
76 | 77 | 78 | 120 | 121 | 122 | 142 | {% endblock %} -------------------------------------------------------------------------------- /groups/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 11:45 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 | <<<<<<< HEAD 19 | name="Group", 20 | fields=[ 21 | ( 22 | "id", 23 | models.BigAutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("name", models.CharField(max_length=100, unique=True)), 31 | ("description", models.TextField(blank=True)), 32 | ("created_at", models.DateTimeField(auto_now_add=True)), 33 | ( 34 | "admins", 35 | models.ManyToManyField( 36 | blank=True, 37 | related_name="admin_groups", 38 | to=settings.AUTH_USER_MODEL, 39 | ), 40 | ), 41 | ( 42 | "members", 43 | models.ManyToManyField( 44 | related_name="joined_groups", to=settings.AUTH_USER_MODEL 45 | ), 46 | ), 47 | ], 48 | ), 49 | migrations.CreateModel( 50 | name="GroupPost", 51 | fields=[ 52 | ( 53 | "id", 54 | models.BigAutoField( 55 | auto_created=True, 56 | primary_key=True, 57 | serialize=False, 58 | verbose_name="ID", 59 | ), 60 | ), 61 | ("content", models.TextField()), 62 | ("created_at", models.DateTimeField(auto_now_add=True)), 63 | ("n_views", models.PositiveIntegerField(default=0)), 64 | ( 65 | "group", 66 | models.ForeignKey( 67 | on_delete=django.db.models.deletion.CASCADE, 68 | related_name="posts", 69 | to="groups.group", 70 | ), 71 | ), 72 | ( 73 | "likes", 74 | models.ManyToManyField( 75 | blank=True, 76 | related_name="liked_group_posts", 77 | to=settings.AUTH_USER_MODEL, 78 | ), 79 | ), 80 | ( 81 | "user", 82 | models.ForeignKey( 83 | on_delete=django.db.models.deletion.CASCADE, 84 | to=settings.AUTH_USER_MODEL, 85 | ), 86 | ), 87 | ], 88 | ), 89 | migrations.CreateModel( 90 | name="GroupMessage", 91 | fields=[ 92 | ( 93 | "id", 94 | models.BigAutoField( 95 | auto_created=True, 96 | primary_key=True, 97 | serialize=False, 98 | verbose_name="ID", 99 | ), 100 | ), 101 | ("content", models.TextField()), 102 | ("timestamp", models.DateTimeField(auto_now_add=True)), 103 | ( 104 | "group", 105 | models.ForeignKey( 106 | on_delete=django.db.models.deletion.CASCADE, 107 | related_name="messages", 108 | to="groups.group", 109 | ), 110 | ), 111 | ( 112 | "sender", 113 | models.ForeignKey( 114 | on_delete=django.db.models.deletion.CASCADE, 115 | related_name="sent_group_messages", 116 | to=settings.AUTH_USER_MODEL, 117 | ), 118 | ), 119 | ======= 120 | name='Group', 121 | fields=[ 122 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 123 | ('name', models.CharField(max_length=100, unique=True)), 124 | ('description', models.TextField(blank=True)), 125 | ('created_at', models.DateTimeField(auto_now_add=True)), 126 | ('admins', models.ManyToManyField(blank=True, related_name='admin_groups', to=settings.AUTH_USER_MODEL)), 127 | ('members', models.ManyToManyField(related_name='joined_groups', to=settings.AUTH_USER_MODEL)), 128 | ], 129 | ), 130 | migrations.CreateModel( 131 | name='GroupPost', 132 | fields=[ 133 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 134 | ('content', models.TextField()), 135 | ('created_at', models.DateTimeField(auto_now_add=True)), 136 | ('n_views', models.PositiveIntegerField(default=0)), 137 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='posts', to='groups.group')), 138 | ('likes', models.ManyToManyField(blank=True, related_name='liked_group_posts', to=settings.AUTH_USER_MODEL)), 139 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 140 | ], 141 | ), 142 | migrations.CreateModel( 143 | name='GroupMessage', 144 | fields=[ 145 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 146 | ('content', models.TextField()), 147 | ('timestamp', models.DateTimeField(auto_now_add=True)), 148 | ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='groups.group')), 149 | ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_group_messages', to=settings.AUTH_USER_MODEL)), 150 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 151 | ], 152 | ), 153 | ] 154 | -------------------------------------------------------------------------------- /users/templates/users/premium_dashboard.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block content %} 5 |
6 |

Premium Dashboard

7 | 8 |
9 | 10 |
11 |
12 |
13 |
Overall Summary
14 |

Total Posts: {{ total_posts }}

15 |

Total Views: {{ total_views }}

16 |

Total Likes: {{ total_likes }}

17 |

Total Comments: {{ total_comments }}

18 |
19 |
20 |
21 | 22 | 23 |
24 |
25 |
26 |
Analytics by Course
27 |
28 |
29 |
30 |
31 |
32 | 33 |

Your Posts

34 | 35 | 36 |
37 |
38 |
39 |
40 | 42 | 43 |
44 |
45 |
46 | 52 |
53 |
54 |
55 | 56 | {% if posts %} 57 | {% for post in posts %} 58 | 74 | {% endfor %} 75 | {% else %} 76 |
No posts available.
77 | {% endif %} 78 |
79 | 80 | 81 | 94 | {% endblock %} 95 | 96 | {% block extra_js %} 97 | 98 | 156 | {% endblock %} -------------------------------------------------------------------------------- /jooustconnectprod/settings.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import os 3 | 4 | BASE_DIR = Path(__file__).resolve().parent.parent 5 | 6 | <<<<<<< HEAD 7 | SECRET_KEY = "django-insecure-$k29@3^!r6@#+#)heu)_$$by$70)!dou!e_8m^o^2nxnf41npg" 8 | 9 | DEBUG = True 10 | 11 | ALLOWED_HOSTS = ["jooustconnect.co.ke", "localhost", "127.0.0.1", "192.168.122.68"] 12 | 13 | CSRF_TRUSTED_ORIGINS = ["https://jooustconnect.co.ke", "http://localhost"] 14 | 15 | CORS_ALLOWED_ORIGINS = [ 16 | "https://jooustconnect.co.ke", 17 | "http://localhost", 18 | "http://192.168.122.68", 19 | ] 20 | 21 | INSTALLED_APPS = [ 22 | "django.contrib.admin", 23 | "django.contrib.auth", 24 | "django.contrib.contenttypes", 25 | "django.contrib.sessions", 26 | "django.contrib.messages", 27 | "django.contrib.staticfiles", 28 | "social", 29 | "groups", 30 | "notifications", 31 | "users", 32 | "messaging", 33 | "channels", 34 | "widget_tweaks", 35 | "corsheaders", 36 | ] 37 | 38 | MIDDLEWARE = [ 39 | "django.middleware.security.SecurityMiddleware", 40 | "django.contrib.sessions.middleware.SessionMiddleware", 41 | "django.middleware.common.CommonMiddleware", 42 | "django.middleware.csrf.CsrfViewMiddleware", 43 | "django.contrib.auth.middleware.AuthenticationMiddleware", 44 | "django.contrib.messages.middleware.MessageMiddleware", 45 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 46 | "corsheaders.middleware.CorsMiddleware", 47 | ] 48 | 49 | ROOT_URLCONF = "jooustconnectprod.urls" 50 | 51 | TEMPLATES = [ 52 | { 53 | "BACKEND": "django.template.backends.django.DjangoTemplates", 54 | "DIRS": [os.path.join(BASE_DIR, "templates")], 55 | "APP_DIRS": True, 56 | "OPTIONS": { 57 | "context_processors": [ 58 | "django.template.context_processors.debug", 59 | "django.template.context_processors.request", 60 | "django.contrib.auth.context_processors.auth", 61 | "django.contrib.messages.context_processors.messages", 62 | "users.context_processors.premium_status", 63 | "notifications.context_processors.notification_count", 64 | ======= 65 | # change this 66 | SECRET_KEY = 'django-insecure-$k29@3^!r6@#+#)heu)_$$by$70)!dou!e_8m^o^2nxnf41npx' 67 | 68 | DEBUG = True 69 | 70 | ALLOWED_HOSTS = ['localhost', '127.0.0.1'] 71 | 72 | CSRF_TRUSTED_ORIGINS = ['http://localhost'] 73 | 74 | CORS_ALLOWED_ORIGINS = [ 75 | 'http://localhost', 76 | ] 77 | 78 | INSTALLED_APPS = [ 79 | 'django.contrib.admin', 80 | 'django.contrib.auth', 81 | 'django.contrib.contenttypes', 82 | 'django.contrib.sessions', 83 | 'django.contrib.messages', 84 | 'django.contrib.staticfiles', 85 | 'social', 86 | 'groups', 87 | 'notifications', 88 | 'users', 89 | 'messaging', 90 | 'channels', 91 | 'widget_tweaks', 92 | 'corsheaders', 93 | ] 94 | 95 | MIDDLEWARE = [ 96 | 'django.middleware.security.SecurityMiddleware', 97 | 'django.contrib.sessions.middleware.SessionMiddleware', 98 | 'django.middleware.common.CommonMiddleware', 99 | 'django.middleware.csrf.CsrfViewMiddleware', 100 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 101 | 'django.contrib.messages.middleware.MessageMiddleware', 102 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 103 | 'corsheaders.middleware.CorsMiddleware', 104 | ] 105 | 106 | ROOT_URLCONF = 'jooustconnectprod.urls' 107 | 108 | TEMPLATES = [ 109 | { 110 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 111 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 112 | 'APP_DIRS': True, 113 | 'OPTIONS': { 114 | 'context_processors': [ 115 | 'django.template.context_processors.debug', 116 | 'django.template.context_processors.request', 117 | 'django.contrib.auth.context_processors.auth', 118 | 'django.contrib.messages.context_processors.messages', 119 | 'users.context_processors.premium_status', 120 | 'notifications.context_processors.notification_count', 121 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 122 | ], 123 | }, 124 | }, 125 | ] 126 | 127 | <<<<<<< HEAD 128 | WSGI_APPLICATION = "jooustconnectprod.wsgi.application" 129 | ASGI_APPLICATION = "jooustconnectprod.asgi.application" 130 | 131 | CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}} 132 | 133 | DATABASES = { 134 | "default": { 135 | "ENGINE": "django.db.backends.sqlite3", 136 | "NAME": BASE_DIR / "db.sqlite3", 137 | ======= 138 | WSGI_APPLICATION = 'jooustconnectprod.wsgi.application' 139 | ASGI_APPLICATION = 'jooustconnectprod.asgi.application' 140 | 141 | CHANNEL_LAYERS = { 142 | 'default': { 143 | 'BACKEND': 'channels.layers.InMemoryChannelLayer' 144 | } 145 | } 146 | 147 | DATABASES = { 148 | 'default': { 149 | 'ENGINE': 'django.db.backends.sqlite3', 150 | 'NAME': BASE_DIR / 'db.sqlite3', 151 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 152 | } 153 | } 154 | 155 | AUTH_PASSWORD_VALIDATORS = [ 156 | { 157 | <<<<<<< HEAD 158 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 159 | }, 160 | { 161 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 162 | }, 163 | { 164 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 165 | }, 166 | { 167 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 168 | }, 169 | ] 170 | 171 | LANGUAGE_CODE = "en-us" 172 | TIME_ZONE = "Africa/Nairobi" 173 | USE_I18N = True 174 | USE_TZ = True 175 | 176 | STATIC_URL = "/static/" 177 | STATICFILES_DIRS = [os.path.join(BASE_DIR, "static")] 178 | STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") 179 | 180 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 181 | MEDIA_URL = "/media/" 182 | MEDIA_ROOT = os.path.join(BASE_DIR, "media") 183 | 184 | AUTH_USER_MODEL = "users.User" 185 | LOGIN_REDIRECT_URL = "feed" 186 | 187 | 188 | SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") 189 | SECURE_SSL_REDIRECT = False 190 | SESSION_COOKIE_SECURE = False 191 | CSRF_COOKIE_SECURE = False 192 | ======= 193 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 194 | }, 195 | { 196 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 197 | }, 198 | { 199 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 200 | }, 201 | { 202 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 203 | }, 204 | ] 205 | 206 | LANGUAGE_CODE = 'en-us' 207 | TIME_ZONE = 'Africa/Nairobi' 208 | USE_I18N = True 209 | USE_TZ = True 210 | 211 | STATIC_URL = '/static/' 212 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 213 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 214 | 215 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 216 | MEDIA_URL = '/media/' 217 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 218 | 219 | AUTH_USER_MODEL = 'users.User' 220 | LOGIN_REDIRECT_URL = 'feed' 221 | 222 | 223 | SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') 224 | SECURE_SSL_REDIRECT = False 225 | SESSION_COOKIE_SECURE = False 226 | CSRF_COOKIE_SECURE = False 227 | 228 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 229 | -------------------------------------------------------------------------------- /groups/templates/groups/group_detail.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | <<<<<<< HEAD 3 | {% load static %} 4 | 5 | {% block title %}{{ group.name }} - JouustConnect{% endblock %} 6 | 7 | {% block extra_css %} 8 | 9 | 16 | {% endblock %} 17 | 18 | {% block content %} 19 |
20 |
21 |
22 |
23 |
24 |

{{ group.name }}

25 |
26 |
27 |

{{ group.description }}

28 |
29 |
Visibility: {{ group.get_visibility_display }}
30 |
Category: {{ group.category }}
31 |
Tags: 32 | {% for tag in group.tags.all %} 33 | {{ tag }} 34 | {% endfor %} 35 |
36 |
37 | 38 |
39 | {% if is_member %} 40 | Leave Group 41 | Group Chat 42 | {% else %} 43 | Join Group 44 | {% endif %} 45 | 46 | {% if is_admin %} 47 | Manage Group 48 | {% endif %} 49 |
50 | 51 | {% if is_member %} 52 |
53 |
54 |

Invite Member

55 |
56 |
57 |
58 | {% csrf_token %} 59 |
60 | 61 | 62 |
63 |
64 |
65 |
66 | 67 |
68 |
69 |

Create a Post

70 |
71 |
72 |
73 | {% csrf_token %} 74 | {{ post_form.as_p }} 75 | 76 |
77 |
78 |
79 | 80 |

Group Posts

81 | {% if posts %} 82 | {% for post in posts %} 83 |
84 |
85 |
{{ post.user.username }}
86 |
87 |
88 |
{{ post.created_at|date:"F d, Y H:i" }}
89 |

{{ post.content }}

90 |
91 |
92 | {% endfor %} 93 | {% else %} 94 | 97 | {% endif %} 98 | {% endif %} 99 |
100 |
101 |
102 |
103 |
104 | {% endblock %} 105 | 106 | {% block extra_js %} 107 | 108 | ======= 109 | 110 | {% block title %}{{ group.name }} - JouustConnect{% endblock %} 111 | 112 | {% block content %} 113 |
114 |
115 |

{{ group.name }}

116 |

{{ group.description }}

117 |

Visibility: {{ group.get_visibility_display }}

118 |

Category: {{ group.category }}

119 |

Tags: {{ group.tags }}

120 | 121 |
122 | {% if is_member %} 123 | Leave Group 124 | Group Chat 125 | {% else %} 126 | Join Group 127 | {% endif %} 128 | 129 | {% if is_admin %} 130 | Manage Group 131 | {% endif %} 132 |
133 | 134 | {% if is_member %} 135 |

Invite Member

136 |
137 | {% csrf_token %} 138 |
139 | 140 | 141 |
142 |
143 | 144 |

Create a Post

145 |
146 | {% csrf_token %} 147 | {{ post_form.as_p }} 148 | 149 |
150 | 151 |

Group Posts

152 | {% if posts %} 153 | {% for post in posts %} 154 |
155 |
156 |
{{ post.user.username }}
157 |
{{ post.created_at }}
158 |

{{ post.content }}

159 |
160 |
161 | {% endfor %} 162 | {% else %} 163 |

No posts yet.

164 | {% endif %} 165 | {% endif %} 166 |
167 |
168 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 169 | {% endblock %} -------------------------------------------------------------------------------- /messaging/views.py: -------------------------------------------------------------------------------- 1 | import json 2 | from django.http import JsonResponse 3 | from django.contrib.auth.decorators import login_required 4 | 5 | from users.models import User 6 | from .models import Message 7 | from django.contrib.auth import get_user_model 8 | from django.db.models import Q 9 | from django.views.decorators.csrf import csrf_exempt 10 | from django.shortcuts import render 11 | from notifications.models import Notification 12 | 13 | 14 | @login_required 15 | def get_messages(request, user_id): 16 | other_user = get_user_model().objects.get(id=user_id) 17 | messages = Message.objects.filter( 18 | <<<<<<< HEAD 19 | (Q(sender=request.user) & Q(receiver=other_user)) 20 | | (Q(sender=other_user) & Q(receiver=request.user)) 21 | ).order_by("timestamp") 22 | 23 | messages_data = [ 24 | { 25 | "id": message.id, 26 | "sender": message.sender.id, 27 | "receiver": message.receiver.id, 28 | "content": message.content, 29 | "timestamp": message.timestamp.isoformat(), 30 | } 31 | for message in messages 32 | ] 33 | 34 | return JsonResponse({"messages": messages_data}) 35 | 36 | 37 | @login_required 38 | def poll_messages(request, user_id): 39 | last_id = request.GET.get("last_id", 0) 40 | try: 41 | last_id = int(last_id) 42 | except ValueError: 43 | return JsonResponse({"error": "Invalid last_id"}, status=400) 44 | 45 | messages = ( 46 | Message.objects.filter(id__gt=last_id) 47 | .filter( 48 | Q(sender=request.user, receiver_id=user_id) 49 | | Q(sender_id=user_id, receiver=request.user) 50 | ) 51 | .order_by("id") 52 | ) 53 | 54 | data = [ 55 | { 56 | "id": msg.id, 57 | "content": msg.content, 58 | "sender": msg.sender.id, 59 | "timestamp": msg.timestamp.isoformat(), 60 | } 61 | for msg in messages 62 | ] 63 | 64 | return JsonResponse({"messages": data}) 65 | 66 | ======= 67 | (Q(sender=request.user) & Q(receiver=other_user)) | 68 | (Q(sender=other_user) & Q(receiver=request.user)) 69 | ).order_by('timestamp') 70 | 71 | messages_data = [ 72 | { 73 | 'id': message.id, 74 | 'sender': message.sender.id, 75 | 'receiver': message.receiver.id, 76 | 'content': message.content, 77 | 'timestamp': message.timestamp.isoformat(), 78 | } 79 | for message in messages 80 | ] 81 | 82 | return JsonResponse({'messages': messages_data}) 83 | 84 | @login_required 85 | def poll_messages(request, user_id): 86 | last_id = request.GET.get('last_id', 0) 87 | try: 88 | last_id = int(last_id) 89 | except ValueError: 90 | return JsonResponse({'error': 'Invalid last_id'}, status=400) 91 | 92 | messages = Message.objects.filter( 93 | id__gt=last_id 94 | ).filter( 95 | Q(sender=request.user, receiver_id=user_id) | 96 | Q(sender_id=user_id, receiver=request.user) 97 | ).order_by('id') 98 | 99 | data = [{ 100 | 'id': msg.id, 101 | 'content': msg.content, 102 | 'sender': msg.sender.id, 103 | 'timestamp': msg.timestamp.isoformat() 104 | } for msg in messages] 105 | 106 | return JsonResponse({'messages': data}) 107 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 108 | 109 | def create_message_notification(user, message_type, related_id, sender_name): 110 | content = f"New message from {sender_name}" 111 | Notification.objects.create( 112 | user=user, 113 | content=content, 114 | notification_type=message_type, 115 | <<<<<<< HEAD 116 | related_id=related_id, 117 | ) 118 | 119 | 120 | @csrf_exempt 121 | @login_required 122 | def send_message(request): 123 | if request.method == "POST": 124 | try: 125 | data = json.loads(request.body) 126 | content = data.get("content") 127 | receiver_id = data.get("receiver_id") 128 | if content and receiver_id: 129 | receiver = User.objects.get( 130 | id=receiver_id 131 | ) # Get the receiver user object 132 | message = Message.objects.create( 133 | sender=request.user, receiver_id=receiver_id, content=content 134 | ) 135 | # Create notification for the receiver 136 | create_message_notification( 137 | receiver, "DM", message.id, request.user.username 138 | ) 139 | return JsonResponse( 140 | { 141 | "status": "ok", 142 | "id": message.id, 143 | "timestamp": message.timestamp.isoformat(), 144 | } 145 | ) 146 | else: 147 | return JsonResponse( 148 | {"status": "error", "message": "Missing content or receiver_id"}, 149 | status=400, 150 | ) 151 | except (json.JSONDecodeError, User.DoesNotExist): 152 | return JsonResponse( 153 | {"status": "error", "message": "Invalid request"}, status=400 154 | ) 155 | return JsonResponse( 156 | {"status": "error", "message": "Invalid request method"}, status=405 157 | ) 158 | 159 | 160 | @login_required 161 | def messages(request, user_id=None): 162 | conversations = ( 163 | Message.objects.filter(Q(sender=request.user) | Q(receiver=request.user)) 164 | .values("sender", "receiver") 165 | .distinct() 166 | ) 167 | 168 | users = [] 169 | for conv in conversations: 170 | if conv["sender"] == request.user.id: 171 | users.append(get_user_model().objects.get(id=conv["receiver"])) 172 | else: 173 | users.append(get_user_model().objects.get(id=conv["sender"])) 174 | 175 | ======= 176 | related_id=related_id 177 | ) 178 | 179 | @csrf_exempt 180 | @login_required 181 | def send_message(request): 182 | if request.method == 'POST': 183 | try: 184 | data = json.loads(request.body) 185 | content = data.get('content') 186 | receiver_id = data.get('receiver_id') 187 | if content and receiver_id: 188 | receiver = User.objects.get(id=receiver_id) # Get the receiver user object 189 | message = Message.objects.create( 190 | sender=request.user, 191 | receiver_id=receiver_id, 192 | content=content 193 | ) 194 | # Create notification for the receiver 195 | create_message_notification(receiver, 'DM', message.id, request.user.username) 196 | return JsonResponse({ 197 | 'status': 'ok', 198 | 'id': message.id, 199 | 'timestamp': message.timestamp.isoformat() 200 | }) 201 | else: 202 | return JsonResponse({'status': 'error', 'message': 'Missing content or receiver_id'}, status=400) 203 | except (json.JSONDecodeError, User.DoesNotExist): 204 | return JsonResponse({'status': 'error', 'message': 'Invalid request'}, status=400) 205 | return JsonResponse({'status': 'error', 'message': 'Invalid request method'}, status=405) 206 | 207 | @login_required 208 | def messages(request, user_id=None): 209 | conversations = Message.objects.filter( 210 | Q(sender=request.user) | Q(receiver=request.user) 211 | ).values('sender', 'receiver').distinct() 212 | 213 | users = [] 214 | for conv in conversations: 215 | if conv['sender'] == request.user.id: 216 | users.append(get_user_model().objects.get(id=conv['receiver'])) 217 | else: 218 | users.append(get_user_model().objects.get(id=conv['sender'])) 219 | 220 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 221 | selected_user = None 222 | if user_id: 223 | selected_user = get_user_model().objects.get(id=user_id) 224 | 225 | <<<<<<< HEAD 226 | return render( 227 | request, 228 | "messaging/messages.html", 229 | { 230 | "users": users, 231 | "selected_user": selected_user, 232 | }, 233 | ) 234 | ======= 235 | return render(request, 'messaging/messages.html', { 236 | 'users': users, 237 | 'selected_user': selected_user, 238 | }) 239 | 240 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 241 | -------------------------------------------------------------------------------- /groups/templates/groups/groups.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | <<<<<<< HEAD 3 | {% load static %} 4 | 5 | {% block title %}Groups - JouustConnect{% endblock %} 6 | 7 | {% block extra_css %} 8 | 9 | 16 | {% endblock %} 17 | 18 | {% block content %} 19 |
20 |
21 |
22 |
23 |
24 |

Discover Groups

25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | All 33 | {% for category in categories %} 34 | {{ category }} 35 | {% endfor %} 36 |
37 |
38 | 44 |
45 |
46 | 47 |
48 | Create New Group 49 |
50 | 51 | {% if page_obj %} 52 |
53 | {% for group in page_obj %} 54 |
55 |
56 |
57 |

{{ group.name }}

58 |

{{ group.category }}

59 |
60 |
61 |

{{ group.description|truncatewords:20 }}

62 |

Members: {{ group.member_count }} | {{ group.get_visibility_display }}

63 |

Tags: {{ group.tags }}

64 |
65 | {% for post in group.recent_posts %} 66 |

{{ post.content|truncatewords:10 }}

67 | {% endfor %} 68 |
69 |
70 | 73 |
74 |
75 | {% endfor %} 76 |
77 | 78 | 79 | 104 | {% else %} 105 |

No groups found.

106 | {% endif %} 107 |
108 |
109 |
110 |
111 |
112 | {% endblock %} 113 | 114 | {% block extra_js %} 115 | ======= 116 | 117 | {% block title %}Groups - JouustConnect{% endblock %} 118 | 119 | {% block content %} 120 |
121 |
122 |

Groups

123 | 124 |
125 |
126 | 127 | 133 | 134 |
135 |
136 | 137 | Create New Group 138 | 139 | {% if groups %} 140 |
141 | {% for group in groups %} 142 |
143 |
144 |
145 |
{{ group.name }}
146 |

{{ group.description|truncatewords:20 }}

147 |

Members: {{ group.member_count }} | Visibility: {{ group.get_visibility_display }}

148 |

Category: {{ group.category }}

149 |

Tags: {{ group.tags }}

150 |
151 |
152 |
153 | {% endfor %} 154 |
155 | {% else %} 156 |

No groups found.

157 | {% endif %} 158 |
159 |
160 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 161 | {% endblock %} -------------------------------------------------------------------------------- /groups/templates/groups/group_messages.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | 3 | {% block extra_css %} 4 | 106 | {% endblock %} 107 | 108 | {% block content %} 109 |
110 |
111 |
112 | {% for member in members %} 113 |
114 | {% if member.profile_picture %} 115 | {{ member.username }} 117 | {% else %} 118 | {{ member.username }} 119 | {% endif %} 120 | {{ member.username }} {% if member in group.admins.all %}(Admin){% endif %} 121 |
122 | {% endfor %} 123 |
124 |
125 |
126 |
127 | Chat in {{ group.name }} 128 |
129 |
130 | 131 |
132 |
133 |
134 |
135 | 136 |
137 | 138 |
139 |
140 |
141 |
142 |
143 |
144 | {% endblock %} 145 | 146 | {% block extra_js %} 147 | 264 | {% endblock %} 265 | -------------------------------------------------------------------------------- /social/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 11:45 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 | <<<<<<< HEAD 19 | name="Post", 20 | fields=[ 21 | ( 22 | "id", 23 | models.BigAutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("content", models.TextField(max_length=150)), 31 | ( 32 | "image", 33 | models.ImageField(blank=True, null=True, upload_to="post_images/"), 34 | ), 35 | ( 36 | "video", 37 | models.FileField(blank=True, null=True, upload_to="post_videos/"), 38 | ), 39 | ("created_at", models.DateTimeField(auto_now_add=True)), 40 | ("n_views", models.PositiveIntegerField(default=0)), 41 | ("is_boosted", models.BooleanField(default=False)), 42 | ("boost_expires_at", models.DateTimeField(blank=True, null=True)), 43 | ( 44 | "likes", 45 | models.ManyToManyField( 46 | blank=True, 47 | related_name="liked_posts", 48 | to=settings.AUTH_USER_MODEL, 49 | ), 50 | ), 51 | ( 52 | "user", 53 | models.ForeignKey( 54 | on_delete=django.db.models.deletion.CASCADE, 55 | to=settings.AUTH_USER_MODEL, 56 | ), 57 | ), 58 | ], 59 | ), 60 | migrations.CreateModel( 61 | name="Report", 62 | fields=[ 63 | ( 64 | "id", 65 | models.BigAutoField( 66 | auto_created=True, 67 | primary_key=True, 68 | serialize=False, 69 | verbose_name="ID", 70 | ), 71 | ), 72 | ( 73 | "report_type", 74 | models.CharField( 75 | choices=[ 76 | ("spam", "Spam"), 77 | ("inappropriate", "Inappropriate Content"), 78 | ("harassment", "Harassment"), 79 | ("other", "Other"), 80 | ], 81 | max_length=20, 82 | ), 83 | ), 84 | ("description", models.TextField(blank=True)), 85 | ("created_at", models.DateTimeField(auto_now_add=True)), 86 | ("is_resolved", models.BooleanField(default=False)), 87 | ( 88 | "post", 89 | models.ForeignKey( 90 | on_delete=django.db.models.deletion.CASCADE, 91 | related_name="reports", 92 | to="social.post", 93 | ), 94 | ), 95 | ( 96 | "reporter", 97 | models.ForeignKey( 98 | on_delete=django.db.models.deletion.CASCADE, 99 | related_name="reports_made", 100 | to=settings.AUTH_USER_MODEL, 101 | ), 102 | ), 103 | ], 104 | ), 105 | migrations.CreateModel( 106 | name="Comment", 107 | fields=[ 108 | ( 109 | "id", 110 | models.BigAutoField( 111 | auto_created=True, 112 | primary_key=True, 113 | serialize=False, 114 | verbose_name="ID", 115 | ), 116 | ), 117 | ("content", models.TextField()), 118 | ("created_at", models.DateTimeField(auto_now_add=True)), 119 | ( 120 | "post", 121 | models.ForeignKey( 122 | on_delete=django.db.models.deletion.CASCADE, 123 | related_name="comments", 124 | to="social.post", 125 | ), 126 | ), 127 | ( 128 | "user", 129 | models.ForeignKey( 130 | on_delete=django.db.models.deletion.CASCADE, 131 | to=settings.AUTH_USER_MODEL, 132 | ), 133 | ), 134 | ], 135 | ), 136 | migrations.CreateModel( 137 | name="PostView", 138 | fields=[ 139 | ( 140 | "id", 141 | models.BigAutoField( 142 | auto_created=True, 143 | primary_key=True, 144 | serialize=False, 145 | verbose_name="ID", 146 | ), 147 | ), 148 | ("viewed_at", models.DateTimeField(auto_now_add=True)), 149 | ( 150 | "post", 151 | models.ForeignKey( 152 | on_delete=django.db.models.deletion.CASCADE, 153 | related_name="views", 154 | to="social.post", 155 | ), 156 | ), 157 | ( 158 | "user", 159 | models.ForeignKey( 160 | on_delete=django.db.models.deletion.CASCADE, 161 | to=settings.AUTH_USER_MODEL, 162 | ), 163 | ), 164 | ], 165 | options={ 166 | "unique_together": {("post", "user")}, 167 | ======= 168 | name='Post', 169 | fields=[ 170 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 171 | ('content', models.TextField(max_length=150)), 172 | ('image', models.ImageField(blank=True, null=True, upload_to='post_images/')), 173 | ('video', models.FileField(blank=True, null=True, upload_to='post_videos/')), 174 | ('created_at', models.DateTimeField(auto_now_add=True)), 175 | ('n_views', models.PositiveIntegerField(default=0)), 176 | ('is_boosted', models.BooleanField(default=False)), 177 | ('boost_expires_at', models.DateTimeField(blank=True, null=True)), 178 | ('likes', models.ManyToManyField(blank=True, related_name='liked_posts', to=settings.AUTH_USER_MODEL)), 179 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 180 | ], 181 | ), 182 | migrations.CreateModel( 183 | name='Report', 184 | fields=[ 185 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 186 | ('report_type', models.CharField(choices=[('spam', 'Spam'), ('inappropriate', 'Inappropriate Content'), ('harassment', 'Harassment'), ('other', 'Other')], max_length=20)), 187 | ('description', models.TextField(blank=True)), 188 | ('created_at', models.DateTimeField(auto_now_add=True)), 189 | ('is_resolved', models.BooleanField(default=False)), 190 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports', to='social.post')), 191 | ('reporter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reports_made', to=settings.AUTH_USER_MODEL)), 192 | ], 193 | ), 194 | migrations.CreateModel( 195 | name='Comment', 196 | fields=[ 197 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 198 | ('content', models.TextField()), 199 | ('created_at', models.DateTimeField(auto_now_add=True)), 200 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='social.post')), 201 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 202 | ], 203 | ), 204 | migrations.CreateModel( 205 | name='PostView', 206 | fields=[ 207 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 208 | ('viewed_at', models.DateTimeField(auto_now_add=True)), 209 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='views', to='social.post')), 210 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 211 | ], 212 | options={ 213 | 'unique_together': {('post', 'user')}, 214 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 215 | }, 216 | ), 217 | ] 218 | -------------------------------------------------------------------------------- /social/views.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | from django.shortcuts import render, redirect 3 | from django.contrib.auth.decorators import login_required 4 | from .models import Post, Comment, PostView, Report 5 | from django.db.models import Q 6 | from .forms import PostForm 7 | from django.views.decorators.http import require_POST 8 | from django.shortcuts import get_object_or_404 9 | from django.utils import timezone 10 | from django.db.models import Count 11 | from django.contrib.auth.decorators import user_passes_test 12 | from django.views.decorators.http import require_POST 13 | 14 | 15 | def is_premium(user): 16 | <<<<<<< HEAD 17 | return ( 18 | user.is_premium 19 | and user.premium_expiry is not None 20 | and user.premium_expiry > timezone.now() 21 | ) 22 | 23 | 24 | def home(request): 25 | return render(request, "social/home.html") 26 | 27 | 28 | @login_required 29 | def feed(request): 30 | posts = ( 31 | Post.objects.select_related("user") 32 | .prefetch_related("likes", "comments", "views") 33 | .order_by("-created_at") 34 | ) 35 | 36 | # Prioritize posts by premium users 37 | premium_posts = posts.filter( 38 | user__is_premium=True, user__premium_expiry__gt=timezone.now() 39 | ) 40 | regular_posts = posts.filter( 41 | Q(user__is_premium=False) | Q(user__premium_expiry__lte=timezone.now()) 42 | ) 43 | 44 | all_posts = list(premium_posts) + list(regular_posts) 45 | return render( 46 | request, "social/feed.html", {"posts": all_posts[:50]} 47 | ) # Limit to 50 posts for performance 48 | ======= 49 | return user.is_premium and user.premium_expiry is not None and user.premium_expiry > timezone.now() 50 | 51 | def home(request): 52 | return render(request, 'social/home.html') 53 | 54 | @login_required 55 | def feed(request): 56 | posts = Post.objects.select_related('user').prefetch_related('likes', 'comments', 'views').order_by('-created_at') 57 | 58 | # Prioritize posts by premium users 59 | premium_posts = posts.filter(user__is_premium=True, user__premium_expiry__gt=timezone.now()) 60 | regular_posts = posts.filter(Q(user__is_premium=False) | Q(user__premium_expiry__lte=timezone.now())) 61 | 62 | all_posts = list(premium_posts) + list(regular_posts) 63 | return render(request, 'social/feed.html', {'posts': all_posts[:50]}) # Limit to 50 posts for performance 64 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 65 | 66 | 67 | @login_required 68 | @require_POST 69 | def like_post(request, post_id): 70 | post = get_object_or_404(Post, id=post_id) 71 | if request.user in post.likes.all(): 72 | post.likes.remove(request.user) 73 | liked = False 74 | else: 75 | post.likes.add(request.user) 76 | liked = True 77 | <<<<<<< HEAD 78 | return JsonResponse({"liked": liked, "n_likes": post.n_likes}) 79 | 80 | ======= 81 | return JsonResponse({'liked': liked, 'n_likes': post.n_likes}) 82 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 83 | 84 | @login_required 85 | @require_POST 86 | def add_comment(request, post_id): 87 | post = get_object_or_404(Post, id=post_id) 88 | <<<<<<< HEAD 89 | content = request.POST.get("content") 90 | comment = Comment.objects.create(post=post, user=request.user, content=content) 91 | return JsonResponse( 92 | { 93 | "status": "success", 94 | "comment": { 95 | "id": comment.id, 96 | "content": comment.content, 97 | "user": comment.user.username, 98 | "created_at": comment.created_at.strftime("%Y-%m-%d %H:%M:%S"), 99 | }, 100 | "n_comments": post.n_comments, 101 | } 102 | ) 103 | 104 | 105 | @login_required 106 | def create_post(request): 107 | if request.method == "POST": 108 | ======= 109 | content = request.POST.get('content') 110 | comment = Comment.objects.create(post=post, user=request.user, content=content) 111 | return JsonResponse({ 112 | 'status': 'success', 113 | 'comment': { 114 | 'id': comment.id, 115 | 'content': comment.content, 116 | 'user': comment.user.username, 117 | 'created_at': comment.created_at.strftime('%Y-%m-%d %H:%M:%S') 118 | }, 119 | 'n_comments': post.n_comments 120 | }) 121 | 122 | @login_required 123 | def create_post(request): 124 | if request.method == 'POST': 125 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 126 | form = PostForm(request.POST, request.FILES) 127 | if form.is_valid(): 128 | post = form.save(commit=False) 129 | post.user = request.user 130 | if is_premium(request.user): 131 | post.is_boosted = True 132 | post.save() 133 | <<<<<<< HEAD 134 | return redirect("feed") 135 | else: 136 | form = PostForm() 137 | return render(request, "social/post_create.html", {"form": form}) 138 | ======= 139 | return redirect('feed') 140 | else: 141 | form = PostForm() 142 | return render(request, 'social/post_create.html', {'form': form}) 143 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 144 | 145 | 146 | @login_required 147 | @require_POST 148 | def delete_post(request, post_id): 149 | post = get_object_or_404(Post, id=post_id, user=request.user) 150 | post.delete() 151 | <<<<<<< HEAD 152 | return JsonResponse({"status": "success"}) 153 | 154 | ======= 155 | return JsonResponse({'status': 'success'}) 156 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 157 | 158 | @login_required 159 | @require_POST 160 | def report_post(request, post_id): 161 | post = get_object_or_404(Post, id=post_id) 162 | <<<<<<< HEAD 163 | report_type = request.POST.get("report_type") 164 | description = request.POST.get("description", "") 165 | ======= 166 | report_type = request.POST.get('report_type') 167 | description = request.POST.get('description', '') 168 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 169 | 170 | report = Report.objects.create( 171 | reporter=request.user, 172 | post=post, 173 | report_type=report_type, 174 | <<<<<<< HEAD 175 | description=description, 176 | ) 177 | 178 | return JsonResponse({"status": "success", "message": "Post reported successfully"}) 179 | ======= 180 | description=description 181 | ) 182 | 183 | return JsonResponse({'status': 'success', 'message': 'Post reported successfully'}) 184 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 185 | 186 | 187 | @login_required 188 | def increment_view_count(request, post_id): 189 | post = get_object_or_404(Post, id=post_id) 190 | <<<<<<< HEAD 191 | 192 | ======= 193 | 194 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 195 | if not PostView.objects.filter(post=post, user=request.user).exists(): 196 | post.n_views += 1 197 | post.save() 198 | PostView.objects.create(post=post, user=request.user) 199 | <<<<<<< HEAD 200 | 201 | return JsonResponse({"n_views": post.n_views}) 202 | 203 | ======= 204 | 205 | return JsonResponse({'n_views': post.n_views}) 206 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 207 | 208 | @login_required 209 | @user_passes_test(is_premium) 210 | def post_viewers(request, post_id): 211 | post = get_object_or_404(Post, id=post_id, user=request.user) 212 | <<<<<<< HEAD 213 | viewers = post.views.select_related("user").order_by("-viewed_at") 214 | 215 | # Get search query from the GET request 216 | query = request.GET.get("query", "") 217 | if query: 218 | viewers = viewers.filter(user__username__icontains=query) 219 | 220 | context = { 221 | "post": post, 222 | "viewers": viewers, 223 | "query": query, 224 | } 225 | return render(request, "social/post_viewers.html", context) 226 | ======= 227 | viewers = post.views.select_related('user').order_by('-viewed_at') 228 | 229 | # Get search query from the GET request 230 | query = request.GET.get('query', '') 231 | if query: 232 | viewers = viewers.filter(user__username__icontains=query) 233 | 234 | context = { 235 | 'post': post, 236 | 'viewers': viewers, 237 | 'query': query, 238 | } 239 | return render(request, 'social/post_viewers.html', context) 240 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 241 | 242 | 243 | @login_required 244 | @user_passes_test(is_premium) 245 | def post_details(request, post_id, detail_type): 246 | post = get_object_or_404(Post, id=post_id, user=request.user) 247 | <<<<<<< HEAD 248 | 249 | if detail_type == "views": 250 | details = ( 251 | PostView.objects.filter(post=post) 252 | .values("user__username") 253 | .annotate(count=Count("id")) 254 | ) 255 | key = "user__username" 256 | elif detail_type == "likes": 257 | details = post.likes.values("username").annotate(count=Count("id")) 258 | key = "username" 259 | elif detail_type == "comments": 260 | details = ( 261 | Comment.objects.filter(post=post) 262 | .values("user__username") 263 | .annotate(count=Count("id")) 264 | ) 265 | key = "user__username" 266 | else: 267 | return JsonResponse({"error": "Invalid detail type"}, status=400) 268 | 269 | details_list = [{"username": item[key], "count": item["count"]} for item in details] 270 | ======= 271 | 272 | if detail_type == 'views': 273 | details = PostView.objects.filter(post=post).values('user__username').annotate(count=Count('id')) 274 | key = 'user__username' 275 | elif detail_type == 'likes': 276 | details = post.likes.values('username').annotate(count=Count('id')) 277 | key = 'username' 278 | elif detail_type == 'comments': 279 | details = Comment.objects.filter(post=post).values('user__username').annotate(count=Count('id')) 280 | key = 'user__username' 281 | else: 282 | return JsonResponse({'error': 'Invalid detail type'}, status=400) 283 | 284 | details_list = [{'username': item[key], 'count': item['count']} for item in details] 285 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 286 | return JsonResponse(details_list, safe=False) 287 | -------------------------------------------------------------------------------- /users/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-08-02 11:12 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | import django.contrib.auth.validators 6 | from django.db import migrations, models 7 | import django.db.models.deletion 8 | import django.utils.timezone 9 | 10 | 11 | class Migration(migrations.Migration): 12 | 13 | initial = True 14 | 15 | dependencies = [ 16 | <<<<<<< HEAD 17 | ("auth", "0012_alter_user_first_name_max_length"), 18 | ======= 19 | ('auth', '0012_alter_user_first_name_max_length'), 20 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 21 | ] 22 | 23 | operations = [ 24 | migrations.CreateModel( 25 | <<<<<<< HEAD 26 | name="User", 27 | fields=[ 28 | ( 29 | "id", 30 | models.BigAutoField( 31 | auto_created=True, 32 | primary_key=True, 33 | serialize=False, 34 | verbose_name="ID", 35 | ), 36 | ), 37 | ("password", models.CharField(max_length=128, verbose_name="password")), 38 | ( 39 | "last_login", 40 | models.DateTimeField( 41 | blank=True, null=True, verbose_name="last login" 42 | ), 43 | ), 44 | ( 45 | "is_superuser", 46 | models.BooleanField( 47 | default=False, 48 | help_text="Designates that this user has all permissions without explicitly assigning them.", 49 | verbose_name="superuser status", 50 | ), 51 | ), 52 | ( 53 | "username", 54 | models.CharField( 55 | error_messages={ 56 | "unique": "A user with that username already exists." 57 | }, 58 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 59 | max_length=150, 60 | unique=True, 61 | validators=[ 62 | django.contrib.auth.validators.UnicodeUsernameValidator() 63 | ], 64 | verbose_name="username", 65 | ), 66 | ), 67 | ( 68 | "first_name", 69 | models.CharField( 70 | blank=True, max_length=150, verbose_name="first name" 71 | ), 72 | ), 73 | ( 74 | "last_name", 75 | models.CharField( 76 | blank=True, max_length=150, verbose_name="last name" 77 | ), 78 | ), 79 | ( 80 | "email", 81 | models.EmailField( 82 | blank=True, max_length=254, verbose_name="email address" 83 | ), 84 | ), 85 | ( 86 | "is_staff", 87 | models.BooleanField( 88 | default=False, 89 | help_text="Designates whether the user can log into this admin site.", 90 | verbose_name="staff status", 91 | ), 92 | ), 93 | ( 94 | "is_active", 95 | models.BooleanField( 96 | default=True, 97 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 98 | verbose_name="active", 99 | ), 100 | ), 101 | ( 102 | "date_joined", 103 | models.DateTimeField( 104 | default=django.utils.timezone.now, verbose_name="date joined" 105 | ), 106 | ), 107 | ("nickname", models.CharField(blank=True, max_length=50)), 108 | ("course", models.CharField(blank=True, max_length=100)), 109 | ("year", models.IntegerField(blank=True, null=True)), 110 | ( 111 | "profile_picture", 112 | models.ImageField(blank=True, upload_to="profile_pics/"), 113 | ), 114 | ("bio", models.TextField(blank=True)), 115 | ("is_verified", models.BooleanField(default=False)), 116 | ("is_premium", models.BooleanField(default=False)), 117 | ("premium_expiry", models.DateTimeField(blank=True, null=True)), 118 | ("privacy_dms", models.BooleanField(default=False)), 119 | ("privacy_posts", models.BooleanField(default=False)), 120 | ( 121 | "groups", 122 | models.ManyToManyField( 123 | blank=True, 124 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 125 | related_name="user_set", 126 | related_query_name="user", 127 | to="auth.group", 128 | verbose_name="groups", 129 | ), 130 | ), 131 | ( 132 | "user_permissions", 133 | models.ManyToManyField( 134 | blank=True, 135 | help_text="Specific permissions for this user.", 136 | related_name="user_set", 137 | related_query_name="user", 138 | to="auth.permission", 139 | verbose_name="user permissions", 140 | ), 141 | ), 142 | ], 143 | options={ 144 | "verbose_name": "user", 145 | "verbose_name_plural": "users", 146 | "abstract": False, 147 | }, 148 | managers=[ 149 | ("objects", django.contrib.auth.models.UserManager()), 150 | ], 151 | ), 152 | migrations.CreateModel( 153 | name="MpesaTransaction", 154 | fields=[ 155 | ( 156 | "id", 157 | models.BigAutoField( 158 | auto_created=True, 159 | primary_key=True, 160 | serialize=False, 161 | verbose_name="ID", 162 | ), 163 | ), 164 | ("transaction_id", models.CharField(max_length=100, unique=True)), 165 | ("amount", models.DecimalField(decimal_places=2, max_digits=10)), 166 | ("phone_number", models.CharField(max_length=15)), 167 | ("transaction_date", models.DateTimeField()), 168 | ("is_used", models.BooleanField(default=False)), 169 | ( 170 | "user", 171 | models.ForeignKey( 172 | blank=True, 173 | null=True, 174 | on_delete=django.db.models.deletion.SET_NULL, 175 | to=settings.AUTH_USER_MODEL, 176 | ), 177 | ), 178 | ======= 179 | name='User', 180 | fields=[ 181 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 182 | ('password', models.CharField(max_length=128, verbose_name='password')), 183 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 184 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 185 | ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), 186 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 187 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 188 | ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), 189 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 190 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 191 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 192 | ('nickname', models.CharField(blank=True, max_length=50)), 193 | ('course', models.CharField(blank=True, max_length=100)), 194 | ('year', models.IntegerField(blank=True, null=True)), 195 | ('profile_picture', models.ImageField(blank=True, upload_to='profile_pics/')), 196 | ('bio', models.TextField(blank=True)), 197 | ('is_verified', models.BooleanField(default=False)), 198 | ('is_premium', models.BooleanField(default=False)), 199 | ('premium_expiry', models.DateTimeField(blank=True, null=True)), 200 | ('privacy_dms', models.BooleanField(default=False)), 201 | ('privacy_posts', models.BooleanField(default=False)), 202 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 203 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 204 | ], 205 | options={ 206 | 'verbose_name': 'user', 207 | 'verbose_name_plural': 'users', 208 | 'abstract': False, 209 | }, 210 | managers=[ 211 | ('objects', django.contrib.auth.models.UserManager()), 212 | ], 213 | ), 214 | migrations.CreateModel( 215 | name='MpesaTransaction', 216 | fields=[ 217 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 218 | ('transaction_id', models.CharField(max_length=100, unique=True)), 219 | ('amount', models.DecimalField(decimal_places=2, max_digits=10)), 220 | ('phone_number', models.CharField(max_length=15)), 221 | ('transaction_date', models.DateTimeField()), 222 | ('is_used', models.BooleanField(default=False)), 223 | ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), 224 | >>>>>>> 20d5f52ef1d7d03304aa3abc20f5e37cc8590b2c 225 | ], 226 | ), 227 | ] 228 | --------------------------------------------------------------------------------