$ python manage.py startapp projectapp
# pragmatic\settings.py
# ...
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'bootstrap4',
'profileapp',
'accountapp',
'articleapp',
'commentapp',
'projectapp',
]
# ...
# pragmatic\urls.py
# ...
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accountapp.urls')),
path('profiles/', include('profileapp.urls')),
path('articles/', include('articleapp.urls')),
path('comments/', include('commentapp.urls')),
path('projects/', include('projectapp.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# projectapp\urls.py
app_name = 'projectapp'
urlpatterns = [
path('list/', ProjectListView.as_view(), name='list'),
path('create/', ProjectCreateView.as_view(), name='create'),
path('detail/<int:pk>', ProjectDetailView.as_view(), name='detail'),
]
# projectapp\models.py
class Project(models.Model):
image = models.ImageField(upload_to='project/', null=False)
title = models.CharField(max_length=20, null=False)
description = models.CharField(max_length=200, null=True)
created_at = models.DateTimeField(auto_now=True)
# projectapp\forms.py
class ProjectCreationForm(ModelForm):
class Meta:
model = Project
fields = ['image', 'title', 'description']
# projectapp\views.py
@method_decorator(login_required, 'get')
@method_decorator(login_required, 'post')
class ProjectCreateView(CreateView):
model = Project
form_class = ProjectCreationForm
template_name = 'projectapp/create.html'
def get_success_url(self):
return reverse('projectapp:detail', kwargs={'pk': self.object.pk})
class ProjectListView(ListView):
model = Project
context_object_name = 'project_list'
template_name = 'projectapp/list.html'
paginate_by = 25
class ProjectDetailView(DetailView):
model = Project
context_object_name = 'target_project'
template_name = 'projectapp/detail.html'
<!-- projectapp\list.html -->
{% extends 'base.html' %}
{% load static %}
{% block content %}
<style>
.container {
padding: 0;
margin: 0, auto;
}
.container div {
display: flex;
justify-content: center;
align-items: center;
border-radius: 1rem;
}
.container img {
width: 7rem;
height: 7rem;
object-fit: cover;
border-radius: 1rem;
}
</style>
{% if project_list %}
<div class="container">
{% for project in project_list %}
<a href="{% url 'projectapp:detail' pk=project.pk %}">
{% include 'projectapp/snippets/card.html' with project=project %}
</a>
{% endfor %}
</div>
<script src="{% static 'js/magic-grid.js' %}"></script>
{% else %}
<div style="text-align:center;">
<h1>No Project YET!</h1>
</div>
{% endif %}
{% include 'projectapp/snippets/pagination.html' with page_obj=page_obj %}
<div style="text-align:center;">
<a href="{% url 'projectapp:create' %}" class="btn btn-dark rounded-pill mt-3 mb-3 px-3">
Create Project
</a>
</div>
{% endblock %}
<!-- projectapp\detail.html -->
{% extends 'base.html' %}
{% block content %}
<div>
<div style="text-align: center; max-width: 500px; margin: 4rem auto;">
<img src="{{ target_project.image.url }}" alt=""
style="height: 12rem; width 12rem; border-radius: 20rem; margin-bottom: 2rem; object-fit: cover;">
<h2 style="font-family: 'NnumSquareB'">
{{ target_project.title }}
</h2>
<h5 style="margin-bottom: 3rem;">
{{ target_project.description }}
</h5>
</div>
</div>
{% endblock %}
<!-- projectapp\create.html -->
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<div style="text-align: center; max-width: 500px; margin: 4rem auto">
<div class="mb-4">
<h4>Create Project</h4>
</div>
<form action="{% url 'projectapp:create' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
{% bootstrap_form form %}
<input type="submit" class="btn btn-dark rounded-pill col-6 mt-3">
</form>
</div>
{% endblock %}
<!-- projectapp\snippets\card.html -->
<div style="display: block; text-align: center;">
<img src="{{ project.image.url }}" alt="">
<h5 class="mt-2" style="font-family: 'NanumSquareB'">
{{ project.title | truncatechars:3 }}
</h5>
</div>
<!-- projectapp\snippets\pagination.html -->
<div style="text-align: center; margin; 1rem 0;">
{% if page_obj.has_previous %}
<a href="{% url 'projectapp:list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-secondary rounded-pill">
{{ page_obj.previous_page_number }}
</a>
{% endif %}
<a href="{% url 'projectapp:list' %}?page={{ page_obj.number }}" class="btn btn-secondary rounded-pill active">
{{ page_obj.number }}
</a>
{% if page_obj.has_next %}}
<a href="{% url 'projectapp:list' %}?page={{ page_obj.next_page_number }}" class="btn btn-secondary rounded-pill">
{{ page_obj.next_page_number }}
</a>
{% endif %}
</div>
<!-- header.html -->
<div class="pragmatic_header">
<div>
<h1 class="pragmatic_logo">Pragmatic</h1>
</div>
<div>
<a href="{% url 'articleapp:list' %}" class="pragmatic_header_nav">
<span>Articles</span>
</a>
{% if not user.is_authenticated %}
<a href="{% url 'accountapp:login' %}?next={{ request.path }}">
<span>login</span>
</a> class=pragmatic_header_nav
<a href="{% url 'accountapp:create' %}" class="pragmatic_header_nav">
<span>SignUp</span>
</a>
{% else %}
<a href="{% url 'accountapp:logout' %}?next{{ request.path }}" class="pragmatic_header_nav">
<span>logout</span>
</a>
<a href="{% url 'accountapp:detail' pk=user.pk %}" class="pragmatic_header_nav">
<span> MyPAge</span>
</a>
{% endif %}
</div>
</div>
$ python manage.py makemigrations
$ python manage.py migrate
$ python .\manage.py runserver