# DJANGO PART 2 ##### personalize items Materi ini berfokus pada Personalisasi Data, di mana setiap user hanya bisa melihat dan mengelola data milik mereka sendiri. Selain itu, dilakukan efisiensi template dengan menggabungkan halaman Detail dan Update menjadi satu tampilan, serta penggunaan Snippet untuk form agar kodingan lebih bersih * edit file views.py ``` from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import LoginRequiredMixin from django.db.models import Q from django.http import HttpResponse, HttpResponseRedirect from django.shortcuts import render, get_object_or_404 from django.views import View from django.views.generic import TemplateView, ListView, DetailView, CreateView, UpdateView from .forms import RestaurantCreateForm, RestaurantLocationCreateForm from .models import RestaurantLocation class RestaurantListView(LoginRequiredMixin, ListView): def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) class RestaurantDetailView(LoginRequiredMixin, DetailView): def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) class RestaurantCreateView(LoginRequiredMixin, CreateView): form_class = RestaurantLocationCreateForm login_url = '/login/' template_name = 'form.html' #success_url = "/restaurants/" def form_valid(self, form): instance = form.save(commit=False) instance.owner = self.request.user return super(RestaurantCreateView, self).form_valid(form) def get_context_data(self, *args, **kwargs): context = super(RestaurantCreateView, self).get_context_data(*args, **kwargs) context['title'] = 'Add Restaurant' return context class RestaurantUpdateView(LoginRequiredMixin, UpdateView): form_class = RestaurantLocationCreateForm login_url = '/login/' template_name = 'restaurants/detail-update.html' #success_url = "/restaurants/" def get_context_data(self, *args, **kwargs): context = super(RestaurantUpdateView, self).get_context_data(*args, **kwargs) name = self.get_object().name context['title'] = f'Update Restaurant: {name}' return context def get_queryset(self): return RestaurantLocation.objects.filter(owner=self.request.user) ``` * membuat file form_snippet.html di template/snippet ``` <form method="POST"> {% csrf_token %} {{ form.as_p }} <button type="submit">Save Changes</button> </form> ``` * edit file item_detail.html ``` {% extends "base.html" %} {% block isi %} <h1>{{ object.name }}</h1> <p>Restaurant: {{ object.restaurant }}</p> <h3>Current Contents:</h3> <ul> {% for item in object.get_contents %}<li>{{ item }}</li>{% endfor %} </ul> <hr/> <h3>Make Changes</h3> {% include 'snippets/form_snippet.html' with form=form %} {% endblock isi %} ``` * update file nav.html ``` <div class='container'> <h1>Webaing.com</h1> <a href='{% url "home" %}'>Home</a> {% if request.user.is_authenticated %} <a href='{% url "restaurant:list" %}'>Restaurants</a> <a href='{% url "menus:list" %}'>Menus</a> <a href='{% url "logout" %}'>Logout</a> {% else %} <a href='{% url "login" %}'>Login</a> {% endif %} </div> ``` * hasilnya ketika kita ke halaman home namun belum login, menu restoran dan items tidak muncul ![image](https://hackmd.io/_uploads/rkWi5pVvWx.png) ##### User Profil View Materi ini membahas pembuatan aplikasi baru bernama Profiles yang berfungsi untuk menampilkan profil publik user * membuat app baru bernama profiles ``` (venv) C:\Dev\trydjango1-11\src\projekaing>python manage.py startapp profiles ``` * edit file views.py ``` from django.contrib.auth import get_user_model from django.http import Http404 from django.shortcuts import get_object_or_404 from django.views.generic import DetailView User = get_user_model() class ProfileDetailView(DetailView): template_name = 'profiles/user.html' def get_object(self): username = self.kwargs.get("username") if username is None: raise Http404 return get_object_or_404(User, username__iexact=username, is_active=True) ``` * edit file base.py ``` INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'menus', 'restaurant', 'profiles', ] ``` * buat file urls.py di profile ![image](https://hackmd.io/_uploads/S1mxgAEv-e.png) * update urls.py utama ``` urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='home.html'), name='home'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), # HARUS ADA INI path('about/', TemplateView.as_view(template_name='about.html'), name='about'), path('contact/', TemplateView.as_view(template_name='contact.html'), name='contact'), path('items/', include('menus.urls', namespace='menus')), path('restaurant/', include('restaurant.urls', namespace='restaurant')), path('u/', include('profiles.urls', namespace='profiles')), ] ``` * membuat tampilan user di template/profiles dengan nama user.html ![image](https://hackmd.io/_uploads/SyZFxANP-g.png) * hasil akhirnya sebagai berikut : ![image](https://hackmd.io/_uploads/rJ6pZRVPZx.png) ##### Style Profile with Bootstrap Materi ini berfokus pada pembenahan tampilan menggunakan Bootstrap 3 * edit user.html ``` {% extends "base.html" %} {% block isi %} <h1>{{ object.username }}</h1> <hr/> <div class="row"> <div class="col-sm-12"> {% for rest in object.restaurantlocation_set.all %} <div class="thumbnail" style="margin-bottom: 15px;"> <div class="caption"> <h4>{{ rest.name }}</h4> <p>{{ rest.location }} | {{ rest.category }}</p> <p><b>Menu Items:</b></p> <ul> {% for item in rest.item_set.all %} <li style="margin-bottom: 10px;"> <b>{{ item.name }}</b> <br/> {{ item.contents }} </li> {% endfor %} </ul> </div> </div> {% empty %} <p class="lead">No restaurants found.</p> {% endfor %} </div> </div> {% endblock isi %} ``` * hasilnya tampilannya akan lebih rapi![image](https://hackmd.io/_uploads/rknc7AEPZl.png) ##### Adding a Robust Search Materi ini membahas pembuatan fitur Pencarian di halaman profil menggunakan Custom Model Manager dan QuerySet. Fitur ini memungkinkan pengunjung mencari kata kunci tertentu dan secara otomatis memfilter daftar restoran serta menu yang ditampilkan pada profil user tersebut * edit views.py di profiles ``` from django.contrib.auth import get_user_model from django.http import Http404 from django.shortcuts import get_object_or_404 from django.views.generic import DetailView from menus.models import Item from restaurant.models import RestaurantLocation User = get_user_model() class ProfileDetailView(DetailView): template_name = 'profiles/user.html' def get_object(self): username = self.kwargs.get("username") return get_object_or_404(User, username__iexact=username, is_active=True) def get_context_data(self, **kwargs): context = super(ProfileDetailView, self).get_context_data(**kwargs) user = context['user'] query = self.request.GET.get('q') qs = RestaurantLocation.objects.filter(owner=user) if query: qs = qs.filter(name__icontains=query) context['locations'] = qs return context ``` * edit file user.html ``` {% extends "base.html" %} {% block isi %} <div class="row"> <div class="col-sm-6 col-sm-offset-3"> <h1>Profile: {{ object.username }}</h1> <form method="GET" action="." class="form-inline" style="margin-bottom: 20px;"> <input type="text" name="q" class="form-control" placeholder="Search..." value="{{ request.GET.q }}"> <button type="submit" class="btn btn-default">Search</button> </form> <hr/> {% for rest in locations %} <div class="thumbnail" style="padding: 15px; margin-bottom: 20px;"> <div class="caption"> <h4>{{ rest.name }} <small><a href="?q={{ rest.location }}">{{ rest.location }}</a></small></h4> <p>Category: <a href="?q={{ rest.category }}">{{ rest.category }}</a></p> <p><b>Menu Items:</b></p> <ul> {% for item in rest.item_set.all %} <li style="margin-bottom: 10px;"> <b>{{ item.name }}</b> <br/> {% for ing in item.get_contents %} <a href="?q={{ ing }}" class="badge">#{{ ing }}</a> {% endfor %} </li> {% endfor %} </ul> </div> </div> {% empty %} <p class="lead text-center">No items or restaurants found.</p> {% endfor %} </div> </div> {% endblock isi %} ``` * hasilnya akan muncul filter pencarian di profiles![image](https://hackmd.io/_uploads/rkJVOAVDWl.png) --- # Section 5: Handling User dan Followers ##### Follow Users part ini membahas pembuatan sistem Follower dan Following dengan cara membuat model Profile untuk setiap user * edit file models.py ``` from django.conf import settings from django.db import models from django.db.models.signals import post_save User = settings.AUTH_USER_MODEL class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) followers = models.ManyToManyField(User, related_name='is_following', blank=True) activated = models.BooleanField(default=False) timestamp = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return self.user.username def post_save_user_receiver(sender, instance, created, *args, **kwargs): if created: profile, is_created = Profile.objects.get_or_create(user=instance) default_user_profile = Profile.objects.filter(id=1).first() if default_user_profile: default_user_profile.followers.add(instance) post_save.connect(post_save_user_receiver, sender=User) ``` * edit file admin.py ``` from django.contrib import admin from .models import Profile admin.site.register(Profile) ``` * hasilnya dapat dilihat di tampilan admin![image](https://hackmd.io/_uploads/rkPloCNP-g.png) ##### Follow Button Form part ini berisikan cara membuat tombol follow dan menangani logika follow * edit file models.py ``` from django.conf import settings from django.db import models from django.db.models.signals import post_save User = settings.AUTH_USER_MODEL class ProfileManager(models.Manager): def toggle_follow(self, request_user, username_to_toggle): profile_ = Profile.objects.get(user__username__iexact=username_to_toggle) user_ = request_user is_following = False if user_ in profile_.followers.all(): profile_.followers.remove(user_) else: profile_.followers.add(user_) is_following = True return profile_, is_following class Profile(models.Model): user = models.OneToOneField(User, on_delete=models.CASCADE) followers = models.ManyToManyField(User, related_name='is_following', blank=True) activated = models.BooleanField(default=False) timestamp = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) def __str__(self): return self.user.username def post_save_user_receiver(sender, instance, created, *args, **kwargs): if created: profile, is_created = Profile.objects.get_or_create(user=instance) default_user_profile = Profile.objects.filter(id=1).first() if default_user_profile: default_user_profile.followers.add(instance) post_save.connect(post_save_user_receiver, sender=User) ``` * edit file views.py ``` from django.contrib.auth import get_user_model from django.http import Http404 from django.shortcuts import get_object_or_404 from django.views.generic import DetailView from menus.models import Item from restaurant.models import RestaurantLocation from django.shortcuts import redirect from django.views import View from .models import Profile from django.contrib.auth.mixins import LoginRequiredMixin User = get_user_model() class ProfileFollowToggle(LoginRequiredMixin, View): def post(self, request, *args, **kwargs): username_to_toggle = request.POST.get("username") profile_, is_following = Profile.objects.toggle_follow(request.user, username_to_toggle) return redirect(f"/u/{profile_.user.username}/") class ProfileDetailView(DetailView): template_name = 'profiles/user.html' def get_object(self): username = self.kwargs.get("username") return get_object_or_404(User, username__iexact=username, is_active=True) def get_context_data(self, **kwargs): context = super(ProfileDetailView, self).get_context_data(**kwargs) user = context['user'] is_following = False if self.request.user.is_authenticated: if user.profile in self.request.user.is_following.all(): is_following = True context['is_following'] = is_following query = self.request.GET.get('q') qs = RestaurantLocation.objects.filter(owner=user) if query: qs = qs.filter(name__icontains=query) context['locations'] = qs return context ``` * edit file urls.py utama ``` from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView from django.contrib.auth.views import LoginView, LogoutView from profiles.views import ProfileFollowToggle urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='home.html'), name='home'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('profile-follow/', ProfileFollowToggle.as_view(), name='follow'), path('about/', TemplateView.as_view(template_name='about.html'), name='about'), path('contact/', TemplateView.as_view(template_name='contact.html'), name='contact'), path('items/', include('menus.urls', namespace='menus')), path('restaurant/', include('restaurant.urls', namespace='restaurant')), path('u/', include('profiles.urls', namespace='profiles')), ] ``` * membuat snippet form di profiles/snippets/follow_form.html ``` {% if request.user.is_authenticated %} {% if request.user != object %} <form method='POST' action='{% url "follow" %}'> {% csrf_token %} <input type='hidden' name='username' value='{{ username }}' /> <button class='btn {% if is_following %}btn-default{% else %}btn-primary{% endif %}'> {% if is_following %}Unfollow{% else %}Follow{% endif %} </button> </form> {% endif %} {% endif %} ``` * edit di user.html ``` {% extends "base.html" %} {% block isi %} <div class="row"> <div class="col-sm-6 col-sm-offset-3"> <h1>Profile: {{ object.username }}</h1> {% include 'snippets/follow_form.html' with username=object.username is_following=is_following %} <hr/> <form method="GET" action="." class="form-inline" style="margin-bottom: 20px;"> <input type="text" name="q" class="form-control" placeholder="Search..." value="{{ request.GET.q }}"> <button type="submit" class="btn btn-default">Search</button> </form> {% for rest in locations %} <div class="thumbnail" style="padding: 15px; margin-bottom: 20px;"> <div class="caption"> <h4>{{ rest.name }} <small><a href="?q={{ rest.location }}">{{ rest.location }}</a></small></h4> <p>Category: <a href="?q={{ rest.category }}">{{ rest.category }}</a></p> <p><b>Menu Items:</b></p> <ul> {% for item in rest.item_set.all %} <li style="margin-bottom: 10px;"> <b>{{ item.name }}</b> <br/> {% for ing in item.get_contents %} <a href="?q={{ ing }}" class="badge">#{{ ing }}</a> {% endfor %} </li> {% endfor %} </ul> </div> </div> {% empty %} <p class="lead text-center">No items or restaurants found.</p> {% endfor %} </div> </div> {% endblock isi %} ``` * hasilnya akan muncul tombol follow![image](https://hackmd.io/_uploads/HkUHVySDWl.png) ##### Following Home Page Feed part ini membuat Home Page yang isinya feed dari orang-orang yang difollow * edit file views.py pada menus ``` from django.contrib.auth.mixins import LoginRequiredMixin from django.shortcuts import render from django.views.generic import View, ListView, DetailView, CreateView, UpdateView from .forms import ItemForm from .models import Item class HomeView(View): def get(self, request, *args, **kwargs): if not request.user.is_authenticated(): object_list = Item.objects.filter(public=True).order_by('-timestamp') return render(request, "home.html", {"object_list": object_list}) user = request.user is_following_user_ids = [x.user.id for x in user.is_following.all()] qs = Item.objects.filter(user__id__in=is_following_user_ids, public=True).order_by("-updated")[:3] return render(request, "menus/home-feed.html", {'object_list': qs}) class ItemListView(ListView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemDetailView(DetailView): def get_queryset(self): return Item.objects.filter(user=self.request.user) class ItemCreateView(CreateView): template_name = 'form.html' form_class = ItemForm def form_valid(self, form): obj = form.save(commit=False) obj.user = self.request.user return super(ItemCreateView, self).form_valid(form) def get_form_kwargs(self): kwargs = super(ItemCreateView, self).get_form_kwargs() kwargs['user'] = self.request.user return kwargs def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemCreateView, self).get_context_data(*args, **kwargs) context['title'] = 'Create Item' return context class ItemUpdateView(LoginRequiredMixin, UpdateView): template_name = 'menus/detail-update.html' form_class = ItemForm def get_queryset(self): return Item.objects.filter(user=self.request.user) def get_context_data(self, *args, **kwargs): context = super(ItemUpdateView, self).get_context_data(*args, **kwargs) context['title'] = 'Update Item' return context ``` * edit file template feed home di menus/templates/menus/home-feed.html ``` {% extends "base.html" %} {% block isi %} <div class="row"> <div class="col-sm-6 col-sm-offset-3"> <h1 class="text-center">User Following Feed</h1> <hr/> {% for object in object_list %} <div class="thumbnail" style="padding: 20px;"> <p><a href="{% url 'profiles:detail' username=object.user.username %}"><b>@{{ object.user.username }}</b></a></p> <h3>{{ object.name }}</h3> <p><b>Restaurant:</b> {{ object.restaurant.title }} ({{ object.restaurant.location }})</p> <p><b>Contents:</b></p> <ul> {% for ing in object.get_contents %} <li>{{ ing }}</li> {% endfor %} </ul> <p class="text-muted"><small>Updated: {{ object.updated|timesince }} ago</small></p> </div> {% empty %} <p class="text-center">You are not following anyone or no public items found.</p> {% endfor %} </div> </div> {% endblock isi %} ``` * update urls.html ``` from django.contrib import admin from django.urls import path, include from django.views.generic import TemplateView from django.contrib.auth.views import LoginView, LogoutView from profiles.views import ProfileFollowToggle from menus.views import HomeView urlpatterns = [ path('admin/', admin.site.urls), path('', TemplateView.as_view(template_name='home.html'), name='home'), path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('profile-follow/', ProfileFollowToggle.as_view(), name='follow'), path('about/', TemplateView.as_view(template_name='about.html'), name='about'), path('contact/', TemplateView.as_view(template_name='contact.html'), name='contact'), path('items/', include('menus.urls', namespace='menus')), path('restaurant/', include('restaurant.urls', namespace='restaurant')), path('u/', include('profiles.urls', namespace='profiles')), path('', HomeView.as_view(), name='home'), ] ```