├── .DS_Store ├── .env.dev ├── .gitignore ├── README.md ├── djangobnb_backend ├── .DS_Store ├── Dockerfile ├── chat │ ├── __init__.py │ ├── admin.py │ ├── api.py │ ├── apps.py │ ├── consumers.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── routing.py │ ├── serializers.py │ ├── tests.py │ ├── token_auth.py │ ├── urls.py │ └── views.py ├── db.sqlite3 ├── djangobnb_backend │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-312.pyc │ │ └── settings.cpython-312.pyc │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── entrypoint.sh ├── manage.py ├── media │ ├── .DS_Store │ └── uploads │ │ ├── .DS_Store │ │ ├── avatars │ │ ├── profile_pic_1.jpg │ │ └── profile_pic_2.jpg │ │ └── properties │ │ ├── beach_1.jpg │ │ ├── cabin_2.jpg │ │ ├── tiny_house_2.jpg │ │ └── villa_1.jpg ├── property │ ├── __init__.py │ ├── admin.py │ ├── api.py │ ├── apps.py │ ├── forms.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_reservation.py │ │ ├── 0003_property_favorited.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── requirements.txt └── useraccount │ ├── __init__.py │ ├── admin.py │ ├── api.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── docker-compose.prod.yml ├── docker-compose.yml └── nginx ├── Dockerfile └── nginx.conf /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/.DS_Store -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | DEBUG=1 2 | SECRET_KEY=codewithstein 3 | DJANGO_ALLOWED_HOSTS=localhost 127.0.0.1 [::1] 4 | SQL_ENGINE=django.db.backends.postgresql 5 | SQL_DATABASE=djangobnb 6 | SQL_USER=postgresuser 7 | SQL_PASSWORD=postgrespassword 8 | SQL_HOST=db 9 | SQL_PORT=5432 10 | DATABASE=postgres -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | djangobnb_backend/env 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Djangobnb - Fullstack Airbnb clone - Next.js 14 / React, Tailwind, Django, Django Rest Framework 2 | 3 | This repository is for the frontend part of my video tutorial series. You can find it here: 4 | [YOUTUBE PLAYLIST](https://www.youtube.com/playlist?list=PLpyspNLjzwBnP-906FBRP5qzB4YXjMvnT) 5 | 6 | ## Things you will learn and implement 7 | 8 | - Next.js 14 9 | - React 10 | - Tailwind 11 | - Django 12 | - Django rest framework 13 | - Docker compose 14 | - Postgresql 15 | - Deployment to Digital Ocean 16 | 17 | ## Features in this project 18 | 19 | - Fully responsive design built with Tailwind 20 | - Authentication using Django Allauth (Email log in) 21 | - How to use react-date-range and other packages 22 | - How to upload images using fetch 23 | - Live chat using web sockets 24 | 25 | ## Getting Started 26 | 27 | First, run the development server: 28 | 29 | ```bash 30 | npm run dev 31 | # or 32 | yarn dev 33 | # or 34 | pnpm dev 35 | # or 36 | bun dev 37 | ``` 38 | 39 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 40 | 41 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 42 | 43 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 44 | 45 | -------------------------------------------------------------------------------- /djangobnb_backend/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/.DS_Store -------------------------------------------------------------------------------- /djangobnb_backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.12.2-slim-bullseye 2 | 3 | WORKDIR /usr/src/djangobnb_backend 4 | 5 | ENV PYTHONDONTWRITEBYTECODE 1 6 | ENV PYTHONUNBUFFERED 1 7 | 8 | RUN apt-get update && apt-get install -y netcat 9 | 10 | RUN pip install --upgrade pip 11 | COPY ./requirements.txt . 12 | RUN pip install -r requirements.txt 13 | 14 | COPY ./entrypoint.sh . 15 | RUN sed -i 's/\r$//g' /usr/src/djangobnb_backend/entrypoint.sh 16 | RUN chmod +x /usr/src/djangobnb_backend/entrypoint.sh 17 | 18 | COPY . . 19 | 20 | ENTRYPOINT [ "/usr/src/djangobnb_backend/entrypoint.sh" ] -------------------------------------------------------------------------------- /djangobnb_backend/chat/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/chat/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/chat/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Conversation, ConversationMessage 4 | 5 | 6 | admin.site.register(Conversation) 7 | admin.site.register(ConversationMessage) -------------------------------------------------------------------------------- /djangobnb_backend/chat/api.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | 3 | from rest_framework.decorators import api_view 4 | 5 | from .models import Conversation, ConversationMessage 6 | from .serializers import ConversationListSerializer, ConversationDetailSerializer, ConversationMessageSerializer 7 | 8 | from useraccount.models import User 9 | 10 | 11 | @api_view(['GET']) 12 | def conversations_list(request): 13 | serializer = ConversationListSerializer(request.user.conversations.all(), many=True) 14 | 15 | return JsonResponse(serializer.data, safe=False) 16 | 17 | 18 | @api_view(['GET']) 19 | def conversations_detail(request, pk): 20 | conversation = request.user.conversations.get(pk=pk) 21 | 22 | conversation_serializer = ConversationDetailSerializer(conversation, many=False) 23 | messages_serializer = ConversationMessageSerializer(conversation.messages.all(), many=True) 24 | 25 | return JsonResponse({ 26 | 'conversation': conversation_serializer.data, 27 | 'messages': messages_serializer.data 28 | }, safe=False) 29 | 30 | @api_view(['GET']) 31 | def conversations_start(request, user_id): 32 | conversations = Conversation.objects.filter(users__in=[user_id]).filter(users__in=[request.user.id]) 33 | 34 | if conversations.count() > 0: 35 | conversation = conversations.first() 36 | 37 | return JsonResponse({'success': True, 'conversation_id': conversation.id}) 38 | else: 39 | user = User.objects.get(pk=user_id) 40 | conversation = Conversation.objects.create() 41 | conversation.users.add(request.user) 42 | conversation.users.add(user) 43 | 44 | return JsonResponse({'success': True, 'conversation_id': conversation.id}) -------------------------------------------------------------------------------- /djangobnb_backend/chat/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ChatConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'chat' 7 | -------------------------------------------------------------------------------- /djangobnb_backend/chat/consumers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from asgiref.sync import sync_to_async 4 | from channels.generic.websocket import AsyncWebsocketConsumer 5 | 6 | from .models import ConversationMessage 7 | 8 | 9 | class ChatConsumer(AsyncWebsocketConsumer): 10 | async def connect(self): 11 | self.room_name = self.scope['url_route']['kwargs']['room_name'] 12 | self.room_group_name = f'chat_{self.room_name}' 13 | 14 | # Join room 15 | 16 | await self.channel_layer.group_add( 17 | self.room_group_name, 18 | self.channel_name 19 | ) 20 | 21 | await self.accept() 22 | 23 | async def disconnect(self): 24 | # Leave room 25 | 26 | await self.channel_layer.group_discard( 27 | self.room_group_name, 28 | self.channel_name 29 | ) 30 | 31 | # Recieve message from web sockets 32 | async def receive(self, text_data): 33 | data = json.loads(text_data) 34 | 35 | conversation_id = data['data']['conversation_id'] 36 | sent_to_id = data['data']['sent_to_id'] 37 | name = data['data']['name'] 38 | body = data['data']['body'] 39 | 40 | await self.channel_layer.group_send( 41 | self.room_group_name, 42 | { 43 | 'type': 'chat_message', 44 | 'body': body, 45 | 'name': name 46 | } 47 | ) 48 | 49 | await self.save_message(conversation_id, body, sent_to_id) 50 | 51 | # Sending messages 52 | async def chat_message(self, event): 53 | body = event['body'] 54 | name = event['name'] 55 | 56 | await self.send(text_data=json.dumps({ 57 | 'body': body, 58 | 'name': name 59 | })) 60 | 61 | @sync_to_async 62 | def save_message(self, conversation_id, body, sent_to_id): 63 | user = self.scope['user'] 64 | 65 | ConversationMessage.objects.create(conversation_id=conversation_id, body=body, sent_to_id=sent_to_id, created_by=user) -------------------------------------------------------------------------------- /djangobnb_backend/chat/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-03-22 20:23 2 | 3 | import django.db.models.deletion 4 | import uuid 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Conversation', 20 | fields=[ 21 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 22 | ('created_at', models.DateTimeField(auto_now_add=True)), 23 | ('modified_at', models.DateTimeField(auto_now=True)), 24 | ('users', models.ManyToManyField(related_name='conversations', to=settings.AUTH_USER_MODEL)), 25 | ], 26 | ), 27 | migrations.CreateModel( 28 | name='ConversationMessage', 29 | fields=[ 30 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 31 | ('body', models.TextField()), 32 | ('created_at', models.DateTimeField(auto_now_add=True)), 33 | ('conversation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='messages', to='chat.conversation')), 34 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sent_messages', to=settings.AUTH_USER_MODEL)), 35 | ('sent_to', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='received_messages', to=settings.AUTH_USER_MODEL)), 36 | ], 37 | ), 38 | ] 39 | -------------------------------------------------------------------------------- /djangobnb_backend/chat/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/chat/migrations/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/chat/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.db import models 4 | 5 | from useraccount.models import User 6 | 7 | 8 | class Conversation(models.Model): 9 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 10 | users = models.ManyToManyField(User, related_name='conversations') 11 | created_at = models.DateTimeField(auto_now_add=True) 12 | modified_at = models.DateTimeField(auto_now=True) 13 | 14 | 15 | class ConversationMessage(models.Model): 16 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 17 | conversation = models.ForeignKey(Conversation, related_name='messages', on_delete=models.CASCADE) 18 | body = models.TextField() 19 | sent_to = models.ForeignKey(User, related_name='received_messages', on_delete=models.CASCADE) 20 | created_by = models.ForeignKey(User, related_name='sent_messages', on_delete=models.CASCADE) 21 | created_at = models.DateTimeField(auto_now_add=True) -------------------------------------------------------------------------------- /djangobnb_backend/chat/routing.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import consumers 4 | 5 | websocket_urlpatterns = [ 6 | path('ws//', consumers.ChatConsumer.as_asgi()), 7 | ] -------------------------------------------------------------------------------- /djangobnb_backend/chat/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from .models import Conversation, ConversationMessage 4 | 5 | from useraccount.serializers import UserDetailSerializer 6 | 7 | 8 | class ConversationListSerializer(serializers.ModelSerializer): 9 | users = UserDetailSerializer(many=True, read_only=True) 10 | 11 | class Meta: 12 | model = Conversation 13 | fields = ('id', 'users', 'modified_at',) 14 | 15 | 16 | class ConversationDetailSerializer(serializers.ModelSerializer): 17 | users = UserDetailSerializer(many=True, read_only=True) 18 | 19 | class Meta: 20 | model = Conversation 21 | fields = ('id', 'users', 'modified_at',) 22 | 23 | 24 | class ConversationMessageSerializer(serializers.ModelSerializer): 25 | sent_to = UserDetailSerializer(many=False, read_only=True) 26 | created_by = UserDetailSerializer(many=False, read_only=True) 27 | 28 | class Meta: 29 | model = ConversationMessage 30 | fields = ('id', 'body', 'sent_to', 'created_by',) -------------------------------------------------------------------------------- /djangobnb_backend/chat/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /djangobnb_backend/chat/token_auth.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.models import AnonymousUser 2 | from channels.db import database_sync_to_async 3 | from channels.middleware import BaseMiddleware 4 | 5 | from rest_framework_simplejwt.tokens import AccessToken 6 | 7 | from useraccount.models import User 8 | 9 | 10 | @database_sync_to_async 11 | def get_user(token_key): 12 | try: 13 | token = AccessToken(token_key) 14 | user_id = token.payload['user_id'] 15 | return User.objects.get(pk=user_id) 16 | except Exception as e: 17 | return AnonymousUser 18 | 19 | 20 | class TokenAuthMiddleware(BaseMiddleware): 21 | def __init__(self, inner): 22 | self.inner = inner 23 | 24 | async def __call__(self, scope, receive, send): 25 | query = dict((x.split('=') for x in scope['query_string'].decode().split('&'))) 26 | token_key = query.get('token') 27 | scope['user'] = await get_user(token_key) 28 | return await super().__call__(scope, receive, send) -------------------------------------------------------------------------------- /djangobnb_backend/chat/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import api 4 | 5 | urlpatterns = [ 6 | path('', api.conversations_list, name='api_conversations_list'), 7 | path('start//', api.conversations_start, name='api_conversations_start'), 8 | path('/', api.conversations_detail, name='api_conversations_detail'), 9 | ] -------------------------------------------------------------------------------- /djangobnb_backend/chat/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /djangobnb_backend/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/db.sqlite3 -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/djangobnb_backend/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/__pycache__/__init__.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/djangobnb_backend/__pycache__/__init__.cpython-312.pyc -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/__pycache__/settings.cpython-312.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/djangobnb_backend/__pycache__/settings.cpython-312.pyc -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/asgi.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from channels.auth import AuthMiddlewareStack 4 | from channels.routing import ProtocolTypeRouter, URLRouter 5 | from channels.security.websocket import AllowedHostsOriginValidator 6 | 7 | from django.core.asgi import get_asgi_application 8 | 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangobnb_backend.settings') 10 | 11 | application = get_asgi_application() 12 | 13 | from chat import routing 14 | from chat.token_auth import TokenAuthMiddleware 15 | 16 | application = ProtocolTypeRouter({ 17 | 'http': get_asgi_application(), 18 | 'websocket': TokenAuthMiddleware( 19 | URLRouter( 20 | routing.websocket_urlpatterns 21 | ) 22 | ) 23 | }) -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from datetime import timedelta 4 | from pathlib import Path 5 | from dotenv import load_dotenv 6 | 7 | load_dotenv() 8 | 9 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 10 | BASE_DIR = Path(__file__).resolve().parent.parent 11 | 12 | 13 | # Quick-start development settings - unsuitable for production 14 | # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ 15 | 16 | # SECURITY WARNING: keep the secret key used in production secret! 17 | SECRET_KEY = os.environ.get("SECRET_KEY") 18 | 19 | # SECURITY WARNING: don't run with debug turned on in production! 20 | DEBUG = True #bool(os.environ.get("DEBUG", default=0)) 21 | 22 | if DEBUG: 23 | ALLOWED_HOSTS = ["localhost", "127.0.0.1", "64.226.81.32"] 24 | else: 25 | ALLOWED_HOSTS = ["64.226.81.32"] 26 | 27 | AUTH_USER_MODEL = 'useraccount.User' 28 | 29 | SITE_ID = 1 30 | 31 | if DEBUG: 32 | WEBSITE_URL = 'http://localhost:8000' 33 | else: 34 | WEBSITE_URL = 'http://64.226.81.32:1337' 35 | 36 | CHANNEL_LAYERS = { 37 | 'default': { 38 | 'BACKEND': 'channels.layers.InMemoryChannelLayer' 39 | } 40 | } 41 | 42 | SIMPLE_JWT = { 43 | "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), 44 | "REFRESH_TOKEN_LIFETIME": timedelta(days=7), 45 | "ROTATE_REFRESH_TOKEN": False, 46 | "BLACKLIST_AFTER_ROTATION": False, 47 | "UPDATE_LAST_LOGIN": True, 48 | "SIGNING_KEY": "acomplexkey", 49 | "ALOGRIGTHM": "HS512", 50 | } 51 | 52 | ACCOUNT_USER_MODEL_USERNAME_FIELD = None 53 | ACCOUNT_EMAIL_REQUIRED = True 54 | ACCOUNT_USERNAME_REQUIRED = False 55 | ACCOUNT_AUTHENTICATION_METHOD = 'email' 56 | ACCOUNT_EMAIL_VERIFICATION = None 57 | 58 | REST_FRAMEWORK = { 59 | 'DEFAULT_AUTHENTICATION_CLASSES': ( 60 | 'rest_framework_simplejwt.authentication.JWTAuthentication', 61 | ), 62 | 'DEFAULT_PERMISSION_CLASSES': ( 63 | 'rest_framework.permissions.IsAuthenticated', 64 | ) 65 | } 66 | 67 | CORS_ALLOWED_ORIGINS = [ 68 | 'http://127.0.0.1:8000', 69 | 'http://127.0.0.1:3000', 70 | 'http://64.226.81.32', 71 | 'http://64.226.81.32:1337' 72 | ] 73 | 74 | CSRF_TRUSTED_ORIGINS = [ 75 | 'http://127.0.0.1:8000', 76 | 'http://127.0.0.1:3000', 77 | 'http://64.226.81.32', 78 | 'http://64.226.81.32:1337' 79 | ] 80 | 81 | CORS_ORIGINS_WHITELIST = [ 82 | 'http://127.0.0.1:8000', 83 | 'http://127.0.0.1:3000', 84 | 'http://64.226.81.32', 85 | 'http://64.226.81.32:1337' 86 | ] 87 | 88 | CORS_ALLOW_ALL_ORIGINS = True 89 | 90 | REST_AUTH = { 91 | "USE_JWT": True, 92 | "JWT_AUTH_HTTPONLY": False 93 | } 94 | 95 | # Application definition 96 | 97 | INSTALLED_APPS = [ 98 | 'daphne', 99 | 'django.contrib.admin', 100 | 'django.contrib.auth', 101 | 'django.contrib.contenttypes', 102 | 'django.contrib.sessions', 103 | 'django.contrib.messages', 104 | 'django.contrib.staticfiles', 105 | 106 | 'rest_framework', 107 | 'rest_framework.authtoken', 108 | 'rest_framework_simplejwt', 109 | 110 | 'allauth', 111 | 'allauth.account', 112 | 113 | 'dj_rest_auth', 114 | 'dj_rest_auth.registration', 115 | 116 | 'corsheaders', 117 | 118 | 'chat', 119 | 'property', 120 | 'useraccount', 121 | ] 122 | 123 | MIDDLEWARE = [ 124 | 'django.middleware.security.SecurityMiddleware', 125 | 'django.contrib.sessions.middleware.SessionMiddleware', 126 | 'corsheaders.middleware.CorsMiddleware', 127 | 'django.middleware.common.CommonMiddleware', 128 | 'django.middleware.csrf.CsrfViewMiddleware', 129 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 130 | 'django.contrib.messages.middleware.MessageMiddleware', 131 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 132 | ] 133 | 134 | ROOT_URLCONF = 'djangobnb_backend.urls' 135 | 136 | TEMPLATES = [ 137 | { 138 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 139 | 'DIRS': [], 140 | 'APP_DIRS': True, 141 | 'OPTIONS': { 142 | 'context_processors': [ 143 | 'django.template.context_processors.debug', 144 | 'django.template.context_processors.request', 145 | 'django.contrib.auth.context_processors.auth', 146 | 'django.contrib.messages.context_processors.messages', 147 | ], 148 | }, 149 | }, 150 | ] 151 | 152 | WSGI_APPLICATION = 'djangobnb_backend.wsgi.application' 153 | ASGI_APPLICATION = 'djangobnb_backend.asgi.application' 154 | 155 | 156 | # Database 157 | # https://docs.djangoproject.com/en/5.0/ref/settings/#databases 158 | 159 | DATABASES = { 160 | 'default': { 161 | 'ENGINE': os.environ.get("SQL_ENGINE"), 162 | 'NAME': os.environ.get("SQL_DATABASE"), 163 | 'USER': os.environ.get("SQL_USER"), 164 | 'PASSWORD': os.environ.get("SQL_PASSWORD"), 165 | 'HOST': os.environ.get("SQL_HOST"), 166 | 'PORT': os.environ.get("SQL_PORT"), 167 | } 168 | } 169 | 170 | 171 | # Password validation 172 | # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators 173 | 174 | AUTH_PASSWORD_VALIDATORS = [ 175 | { 176 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 177 | }, 178 | { 179 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 180 | }, 181 | { 182 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 183 | }, 184 | { 185 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 186 | }, 187 | ] 188 | 189 | 190 | # Internationalization 191 | # https://docs.djangoproject.com/en/5.0/topics/i18n/ 192 | 193 | LANGUAGE_CODE = 'en-us' 194 | 195 | TIME_ZONE = 'UTC' 196 | 197 | USE_I18N = True 198 | 199 | USE_TZ = True 200 | 201 | 202 | # Static files (CSS, JavaScript, Images) 203 | # https://docs.djangoproject.com/en/5.0/howto/static-files/ 204 | 205 | STATIC_URL = 'static/' 206 | MEDIA_URL = 'media/' 207 | MEDIA_ROOT = BASE_DIR / 'media' 208 | 209 | # Default primary key field type 210 | # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field 211 | 212 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 213 | -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import path, include 5 | 6 | urlpatterns = [ 7 | path('admin/', admin.site.urls), 8 | path('api/properties/', include('property.urls')), 9 | path('api/auth/', include('useraccount.urls')), 10 | path('api/chat/', include('chat.urls')), 11 | ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 12 | -------------------------------------------------------------------------------- /djangobnb_backend/djangobnb_backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for djangobnb_backend 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/5.0/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangobnb_backend.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /djangobnb_backend/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$DATABASE" = "postgres" ] 4 | then 5 | echo "Check if database is running..." 6 | 7 | while ! nc -z $SQL_HOST $SQL_PORT; do 8 | sleep 0.1 9 | done 10 | 11 | echo "The database is up and running :-D" 12 | fi 13 | 14 | python manage.py makemigrations 15 | python manage.py migrate 16 | 17 | exec "$@" -------------------------------------------------------------------------------- /djangobnb_backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangobnb_backend.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /djangobnb_backend/media/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/.DS_Store -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/.DS_Store -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/avatars/profile_pic_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/avatars/profile_pic_1.jpg -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/avatars/profile_pic_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/avatars/profile_pic_2.jpg -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/properties/beach_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/properties/beach_1.jpg -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/properties/cabin_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/properties/cabin_2.jpg -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/properties/tiny_house_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/properties/tiny_house_2.jpg -------------------------------------------------------------------------------- /djangobnb_backend/media/uploads/properties/villa_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/media/uploads/properties/villa_1.jpg -------------------------------------------------------------------------------- /djangobnb_backend/property/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/property/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/property/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import Property, Reservation 4 | 5 | 6 | admin.site.register(Property) 7 | admin.site.register(Reservation) -------------------------------------------------------------------------------- /djangobnb_backend/property/api.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | 3 | from rest_framework.decorators import api_view, authentication_classes, permission_classes 4 | from rest_framework_simplejwt.tokens import AccessToken 5 | from .forms import PropertyForm 6 | from .models import Property, Reservation 7 | from .serializers import PropertiesListSerializer, PropertiesDetailSerializer, ReservationsListSerializer 8 | from useraccount.models import User 9 | 10 | @api_view(['GET']) 11 | @authentication_classes([]) 12 | @permission_classes([]) 13 | def properties_list(request): 14 | # 15 | # Auth 16 | 17 | try: 18 | token = request.META['HTTP_AUTHORIZATION'].split('Bearer ')[1] 19 | token = AccessToken(token) 20 | user_id = token.payload['user_id'] 21 | user = User.objects.get(pk=user_id) 22 | except Exception as e: 23 | user = None 24 | 25 | # 26 | # 27 | 28 | favorites = [] 29 | properties = Property.objects.all() 30 | 31 | # 32 | # Filter 33 | 34 | is_favorites = request.GET.get('is_favorites', '') 35 | landlord_id = request.GET.get('landlord_id', '') 36 | 37 | country = request.GET.get('country', '') 38 | category = request.GET.get('category', '') 39 | checkin_date = request.GET.get('checkIn', '') 40 | checkout_date = request.GET.get('checkOut', '') 41 | bedrooms = request.GET.get('numBedrooms', '') 42 | guests = request.GET.get('numGuests', '') 43 | bathrooms = request.GET.get('numBathrooms', '') 44 | 45 | print('country', country) 46 | 47 | if checkin_date and checkout_date: 48 | exact_matches = Reservation.objects.filter(start_date=checkin_date) | Reservation.objects.filter(end_date=checkout_date) 49 | overlap_matches = Reservation.objects.filter(start_date__lte=checkout_date, end_date__gte=checkin_date) 50 | all_matches = [] 51 | 52 | for reservation in exact_matches | overlap_matches: 53 | all_matches.append(reservation.property_id) 54 | 55 | properties = properties.exclude(id__in=all_matches) 56 | 57 | if landlord_id: 58 | properties = properties.filter(landlord_id=landlord_id) 59 | 60 | if is_favorites: 61 | properties = properties.filter(favorited__in=[user]) 62 | 63 | if guests: 64 | properties = properties.filter(guests__gte=guests) 65 | 66 | if bedrooms: 67 | properties = properties.filter(bedrooms__gte=bedrooms) 68 | 69 | if bathrooms: 70 | properties = properties.filter(bathrooms__gte=bathrooms) 71 | 72 | if country: 73 | properties = properties.filter(country=country) 74 | 75 | if category and category != 'undefined': 76 | properties = properties.filter(category=category) 77 | 78 | # 79 | # Favorites 80 | 81 | if user: 82 | for property in properties: 83 | if user in property.favorited.all(): 84 | favorites.append(property.id) 85 | 86 | # 87 | # 88 | 89 | serializer = PropertiesListSerializer(properties, many=True) 90 | 91 | return JsonResponse({ 92 | 'data': serializer.data, 93 | 'favorites': favorites 94 | }) 95 | 96 | 97 | @api_view(['GET']) 98 | @authentication_classes([]) 99 | @permission_classes([]) 100 | def properties_detail(request, pk): 101 | property = Property.objects.get(pk=pk) 102 | 103 | serializer = PropertiesDetailSerializer(property, many=False) 104 | 105 | return JsonResponse(serializer.data) 106 | 107 | 108 | @api_view(['GET']) 109 | @authentication_classes([]) 110 | @permission_classes([]) 111 | def property_reservations(request, pk): 112 | property = Property.objects.get(pk=pk) 113 | reservations = property.reservations.all() 114 | 115 | serializer = ReservationsListSerializer(reservations, many=True) 116 | 117 | return JsonResponse(serializer.data, safe=False) 118 | 119 | 120 | @api_view(['POST', 'FILES']) 121 | def create_property(request): 122 | form = PropertyForm(request.POST, request.FILES) 123 | 124 | if form.is_valid(): 125 | property = form.save(commit=False) 126 | property.landlord = request.user 127 | property.save() 128 | 129 | return JsonResponse({'success': True}) 130 | else: 131 | print('error', form.errors, form.non_field_errors) 132 | return JsonResponse({'errors': form.errors.as_json()}, status=400) 133 | 134 | 135 | @api_view(['POST']) 136 | def book_property(request, pk): 137 | try: 138 | start_date = request.POST.get('start_date', '') 139 | end_date = request.POST.get('end_date', '') 140 | number_of_nights = request.POST.get('number_of_nights', '') 141 | total_price = request.POST.get('total_price', '') 142 | guests = request.POST.get('guests', '') 143 | 144 | property = Property.objects.get(pk=pk) 145 | 146 | Reservation.objects.create( 147 | property=property, 148 | start_date=start_date, 149 | end_date=end_date, 150 | number_of_nights=number_of_nights, 151 | total_price=total_price, 152 | guests=guests, 153 | created_by=request.user 154 | ) 155 | 156 | return JsonResponse({'success': True}) 157 | except Exception as e: 158 | print('Error', e) 159 | 160 | return JsonResponse({'success': False}) 161 | 162 | 163 | @api_view(['POST']) 164 | def toggle_favorite(request, pk): 165 | property = Property.objects.get(pk=pk) 166 | 167 | if request.user in property.favorited.all(): 168 | property.favorited.remove(request.user) 169 | 170 | return JsonResponse({'is_favorite': False}) 171 | else: 172 | property.favorited.add(request.user) 173 | 174 | return JsonResponse({'is_favorite': True}) -------------------------------------------------------------------------------- /djangobnb_backend/property/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PropertyConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'property' 7 | -------------------------------------------------------------------------------- /djangobnb_backend/property/forms.py: -------------------------------------------------------------------------------- 1 | from django.forms import ModelForm 2 | 3 | from .models import Property 4 | 5 | 6 | class PropertyForm(ModelForm): 7 | class Meta: 8 | model = Property 9 | fields = ( 10 | 'title', 11 | 'description', 12 | 'price_per_night', 13 | 'bedrooms', 14 | 'bathrooms', 15 | 'guests', 16 | 'country', 17 | 'country_code', 18 | 'category', 19 | 'image', 20 | ) -------------------------------------------------------------------------------- /djangobnb_backend/property/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-24 19:49 2 | 3 | import django.db.models.deletion 4 | import uuid 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name='Property', 20 | fields=[ 21 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 22 | ('title', models.CharField(max_length=255)), 23 | ('description', models.TextField()), 24 | ('price_per_night', models.IntegerField()), 25 | ('bedrooms', models.IntegerField()), 26 | ('bathrooms', models.IntegerField()), 27 | ('guests', models.IntegerField()), 28 | ('country', models.CharField(max_length=255)), 29 | ('country_code', models.CharField(max_length=10)), 30 | ('category', models.CharField(max_length=255)), 31 | ('image', models.ImageField(upload_to='uploads/properties')), 32 | ('created_at', models.DateTimeField(auto_now_add=True)), 33 | ('landlord', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='properties', to=settings.AUTH_USER_MODEL)), 34 | ], 35 | ), 36 | ] 37 | -------------------------------------------------------------------------------- /djangobnb_backend/property/migrations/0002_reservation.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-03-18 17:05 2 | 3 | import django.db.models.deletion 4 | import uuid 5 | from django.conf import settings 6 | from django.db import migrations, models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('property', '0001_initial'), 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='Reservation', 19 | fields=[ 20 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 21 | ('start_date', models.DateField()), 22 | ('end_date', models.DateField()), 23 | ('number_of_nights', models.IntegerField()), 24 | ('guests', models.IntegerField()), 25 | ('total_price', models.FloatField()), 26 | ('created_at', models.DateTimeField(auto_now_add=True)), 27 | ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to=settings.AUTH_USER_MODEL)), 28 | ('property', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reservations', to='property.property')), 29 | ], 30 | ), 31 | ] 32 | -------------------------------------------------------------------------------- /djangobnb_backend/property/migrations/0003_property_favorited.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-03-18 18:42 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 | ('property', '0002_reservation'), 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='property', 17 | name='favorited', 18 | field=models.ManyToManyField(blank=True, related_name='favorites', to=settings.AUTH_USER_MODEL), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /djangobnb_backend/property/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/property/migrations/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/property/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.conf import settings 4 | from django.db import models 5 | 6 | from useraccount.models import User 7 | 8 | 9 | class Property(models.Model): 10 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 11 | title = models.CharField(max_length=255) 12 | description = models.TextField() 13 | price_per_night = models.IntegerField() 14 | bedrooms = models.IntegerField() 15 | bathrooms = models.IntegerField() 16 | guests = models.IntegerField() 17 | country = models.CharField(max_length=255) 18 | country_code = models.CharField(max_length=10) 19 | category = models.CharField(max_length=255) 20 | favorited = models.ManyToManyField(User, related_name='favorites', blank=True) 21 | image = models.ImageField(upload_to='uploads/properties') 22 | landlord = models.ForeignKey(User, related_name='properties', on_delete=models.CASCADE) 23 | created_at = models.DateTimeField(auto_now_add=True) 24 | 25 | def image_url(self): 26 | return f'{settings.WEBSITE_URL}{self.image.url}' 27 | 28 | 29 | class Reservation(models.Model): 30 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 31 | property = models.ForeignKey(Property, related_name='reservations', on_delete=models.CASCADE) 32 | start_date = models.DateField() 33 | end_date = models.DateField() 34 | number_of_nights = models.IntegerField() 35 | guests = models.IntegerField() 36 | total_price = models.FloatField() 37 | created_by = models.ForeignKey(User, related_name='reservations', on_delete=models.CASCADE) 38 | created_at = models.DateTimeField(auto_now_add=True) -------------------------------------------------------------------------------- /djangobnb_backend/property/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from .models import Property, Reservation 4 | 5 | from useraccount.serializers import UserDetailSerializer 6 | 7 | 8 | class PropertiesListSerializer(serializers.ModelSerializer): 9 | class Meta: 10 | model = Property 11 | fields = ( 12 | 'id', 13 | 'title', 14 | 'price_per_night', 15 | 'image_url', 16 | ) 17 | 18 | 19 | class PropertiesDetailSerializer(serializers.ModelSerializer): 20 | landlord = UserDetailSerializer(read_only=True, many=False) 21 | 22 | class Meta: 23 | model = Property 24 | fields = ( 25 | 'id', 26 | 'title', 27 | 'description', 28 | 'price_per_night', 29 | 'image_url', 30 | 'bedrooms', 31 | 'bathrooms', 32 | 'guests', 33 | 'landlord' 34 | ) 35 | 36 | 37 | class ReservationsListSerializer(serializers.ModelSerializer): 38 | property = PropertiesListSerializer(read_only=True, many=False) 39 | 40 | class Meta: 41 | model = Reservation 42 | fields = ( 43 | 'id', 'start_date', 'end_date', 'number_of_nights', 'total_price', 'property' 44 | ) -------------------------------------------------------------------------------- /djangobnb_backend/property/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /djangobnb_backend/property/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from . import api 4 | 5 | 6 | urlpatterns = [ 7 | path('', api.properties_list, name='api_properties_list'), 8 | path('create/', api.create_property, name='api_create_property'), 9 | path('/', api.properties_detail, name='api_properties_detail'), 10 | path('/book/', api.book_property, name='api_book_property'), 11 | path('/reservations/', api.property_reservations, name='api_property_reservations'), 12 | path('/toggle_favorite/', api.toggle_favorite, name='api_toggle_favorite'), 13 | ] -------------------------------------------------------------------------------- /djangobnb_backend/property/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /djangobnb_backend/requirements.txt: -------------------------------------------------------------------------------- 1 | Django==5.0.2 2 | psycopg2-binary==2.9.9 3 | dj-rest-auth==4.0.0 4 | django-allauth==0.52.0 5 | django-cors-headers==4.3.1 6 | djangorestframework==3.14.0 7 | djangorestframework-simplejwt==5.3.1 8 | pillow==10.2.0 9 | channels==4.0.0 10 | daphne==4.0.0 11 | gunicorn==21.2.0 12 | python-dotenv==1.0.1 -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/useraccount/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | from .models import User 4 | 5 | admin.site.register(User) -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/api.py: -------------------------------------------------------------------------------- 1 | from django.http import JsonResponse 2 | 3 | from rest_framework.decorators import api_view, authentication_classes, permission_classes 4 | 5 | from .models import User 6 | from .serializers import UserDetailSerializer 7 | 8 | from property.serializers import ReservationsListSerializer 9 | 10 | 11 | @api_view(['GET']) 12 | @authentication_classes([]) 13 | @permission_classes([]) 14 | def landlord_detail(request, pk): 15 | user = User.objects.get(pk=pk) 16 | 17 | serializer = UserDetailSerializer(user, many=False) 18 | 19 | return JsonResponse(serializer.data, safe=False) 20 | 21 | 22 | @api_view(['GET']) 23 | def reservations_list(request): 24 | reservations = request.user.reservations.all() 25 | 26 | print('user', request.user) 27 | print(reservations) 28 | 29 | serializer = ReservationsListSerializer(reservations, many=True) 30 | return JsonResponse(serializer.data, safe=False) -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class UseraccountConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'useraccount' 7 | -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2024-02-24 19:23 2 | 3 | import useraccount.models 4 | import uuid 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ('auth', '0012_alter_user_first_name_max_length'), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='User', 19 | fields=[ 20 | ('password', models.CharField(max_length=128, verbose_name='password')), 21 | ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), 22 | ('email', models.EmailField(max_length=254, unique=True)), 23 | ('name', models.CharField(blank=True, max_length=255, null=True)), 24 | ('avatar', models.ImageField(upload_to='uploads/avatars')), 25 | ('is_active', models.BooleanField(default=True)), 26 | ('is_superuser', models.BooleanField(default=False)), 27 | ('is_staff', models.BooleanField(default=False)), 28 | ('date_joined', models.DateTimeField(auto_now_add=True)), 29 | ('last_login', models.DateTimeField(blank=True, null=True)), 30 | ('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')), 31 | ('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')), 32 | ], 33 | options={ 34 | 'abstract': False, 35 | }, 36 | managers=[ 37 | ('objects', useraccount.models.CustomUserManager()), 38 | ], 39 | ), 40 | ] 41 | -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SteinOveHelset/djangobnb_backend/7d6c73fb2cf932997696676f9dc1a25ad1150776/djangobnb_backend/useraccount/migrations/__init__.py -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/models.py: -------------------------------------------------------------------------------- 1 | import uuid 2 | 3 | from django.conf import settings 4 | from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager 5 | from django.db import models 6 | 7 | 8 | class CustomUserManager(UserManager): 9 | def _create_user(self, name, email, password, **extra_fields): 10 | if not email: 11 | raise ValueError("You have not specified a valid e-mail address") 12 | 13 | email = self.normalize_email(email) 14 | user = self.model(email=email, name=name, **extra_fields) 15 | user.set_password(password) 16 | user.save(using=self.db) 17 | 18 | return user 19 | 20 | def create_user(self, name=None, email=None, password=None, **extra_fields): 21 | extra_fields.setdefault('is_staff', False) 22 | extra_fields.setdefault('is_superuser', False) 23 | return self._create_user(name, email, password, **extra_fields) 24 | 25 | def create_superuser(self, name=None, email=None, password=None, **extra_fields): 26 | extra_fields.setdefault('is_staff', True) 27 | extra_fields.setdefault('is_superuser', True) 28 | return self._create_user(name, email, password, **extra_fields) 29 | 30 | 31 | class User(AbstractBaseUser, PermissionsMixin): 32 | id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) 33 | email = models.EmailField(unique=True) 34 | name = models.CharField(max_length=255, blank=True, null=True) 35 | avatar = models.ImageField(upload_to='uploads/avatars') 36 | 37 | is_active = models.BooleanField(default=True) 38 | is_superuser = models.BooleanField(default=False) 39 | is_staff = models.BooleanField(default=False) 40 | 41 | date_joined = models.DateTimeField(auto_now_add=True) 42 | last_login = models.DateTimeField(blank=True, null=True) 43 | 44 | objects = CustomUserManager() 45 | 46 | USERNAME_FIELD = 'email' 47 | EMAIL_FIELD = 'email' 48 | REQUIRED_FIELDS = ['name',] 49 | 50 | def avatar_url(self): 51 | if self.avatar: 52 | return f'{settings.WEBSITE_URL}{self.avatar.url}' 53 | else: 54 | return '' -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | 3 | from .models import User 4 | 5 | class UserDetailSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = User 8 | fields = ( 9 | 'id', 'name', 'avatar_url' 10 | ) -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | 3 | from dj_rest_auth.jwt_auth import get_refresh_view 4 | from dj_rest_auth.registration.views import RegisterView 5 | from dj_rest_auth.views import LoginView, LogoutView, UserDetailsView 6 | from rest_framework_simplejwt.views import TokenVerifyView 7 | 8 | from . import api 9 | 10 | urlpatterns = [ 11 | path('register/', RegisterView.as_view(), name='rest_register'), 12 | path('login/', LoginView.as_view(), name='rest_login'), 13 | path('logout/', LogoutView.as_view(), name='rest_logout'), 14 | path('token/refresh/', get_refresh_view().as_view(), name='token_refresh'), 15 | path('myreservations/', api.reservations_list, name='api_reservations_list'), 16 | path('/', api.landlord_detail, name='api_landlrod_detail'), 17 | ] -------------------------------------------------------------------------------- /djangobnb_backend/useraccount/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /docker-compose.prod.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | nginx: 5 | build: ./nginx 6 | ports: 7 | - 1337:80 8 | depends_on: 9 | - web 10 | volumes: 11 | - media_volume:/usr/src/djangobnb_backend/media 12 | web: 13 | build: ./djangobnb_backend 14 | command: gunicorn djangobnb_backend.wsgi:application --bind 0.0.0.0:8000 15 | volumes: 16 | - ./djangobnb_backend/:/usr/src/djangobnb_backend/ 17 | - media_volume:/usr/src/djangobnb_backend/media 18 | expose: 19 | - 8000 20 | env_file: 21 | - ./.env.dev 22 | depends_on: 23 | - db 24 | - daphne 25 | daphne: 26 | build: ./djangobnb_backend 27 | command: daphne --bind 0.0.0.0 -p 8002 djangobnb_backend.asgi:application 28 | ports: 29 | - 8002:8002 30 | db: 31 | image: postgres:15 32 | volumes: 33 | - postgres_data:/var/lib/postgresql/data/ 34 | environment: 35 | - POSTGRES_USER=postgresuser 36 | - POSTGRES_PASSWORD=postgrespassword 37 | - POSTGRES_DB=djangobnb 38 | 39 | volumes: 40 | postgres_data: 41 | media_volume: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | web: 5 | build: ./djangobnb_backend 6 | command: python manage.py runserver 0.0.0.0:8000 7 | volumes: 8 | - ./djangobnb_backend/:/usr/src/djangobnb_backend/ 9 | ports: 10 | - 8000:8000 11 | env_file: 12 | - ./.env.dev 13 | depends_on: 14 | - db 15 | db: 16 | image: postgres:15 17 | volumes: 18 | - postgres_data:/var/lib/postgresql/data/ 19 | environment: 20 | - POSTGRES_USER=postgresuser 21 | - POSTGRES_PASSWORD=postgrespassword 22 | - POSTGRES_DB=djangobnb 23 | 24 | volumes: 25 | postgres_data: -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.25 2 | 3 | RUN rm /etc/nginx/conf.d/default.conf 4 | COPY nginx.conf /etc/nginx/conf.d -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | upstream djangobnb_backend { 2 | server web:8000; 3 | } 4 | 5 | server { 6 | listen 80; 7 | 8 | location / { 9 | proxy_pass http://djangobnb_backend; 10 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 11 | proxy_set_header Host $host; 12 | proxy_redirect off; 13 | } 14 | 15 | location /media/ { 16 | alias /usr/src/djangobnb_backend/media/; 17 | } 18 | 19 | location ~^/ws/ { 20 | proxy_pass http://127.0.0.1:8002; 21 | proxy_http_version 1.1; 22 | proxy_set_header Upgrade $http_upgrade; 23 | proxy_set_header Connection 'upgrade'; 24 | proxy_set_header Host $host; 25 | proxy_cache_bypass $http_upgrade; 26 | } 27 | } --------------------------------------------------------------------------------