# 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 
##### 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

* 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

* hasil akhirnya sebagai berikut : 
##### 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
##### 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
---
# 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
##### 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
##### 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'),
]
```