added messages support, more clean html/bootstrap

This commit is contained in:
wcolmenares 2019-03-18 23:29:07 -04:00
parent 318feb7e97
commit e1f6537165
11 changed files with 385 additions and 117 deletions

View File

@ -13,19 +13,43 @@
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<div class="d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm">
<h5 class="my-0 mr-md-auto font-weight-normal"><a href="{% url 'jobs:index' %}"><img src="{% static 'img/logo.svg' %}"/></a></h5>
<nav class="my-2 my-md-0 mr-md-3">
{# <a class="p-2 text-dark" href="#">Search</a>#}
{# <a class="p-2 text-dark" href="#">Tags</a>#}
<a class="p-2 text-dark" href="{% url 'jobs:job_list' %}">Jobs</a>
</nav>
<a class="btn btn-outline-primary" href="{% url 'jobs:job_create' %}">Post a job</a>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{% url 'jobs:index' %}"><img src="{% static 'img/logo.svg' %}" alt=""></a>
{% if request.user.is_authenticated %}
<a class="btn text-dark" href="{% url 'logout' %}">Logout</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Applications</a>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuLink">
<a class="dropdown-item" href="{% url 'jobs:my_applications' %}">Your applications</a>
<a class="dropdown-item" href="{% url 'jobs:applications_others' %}">Applications to your jobs</a>
</ul>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'jobs:messages_inbox' %}">Messages</a>
</li>
<li class="nav-item justify-content-end">
<a class="nav-link" href="{% url 'jobs:my_jobs' %}">Your Jobs</a>
{# {% url 'jobs:job_list' %}#}
</li>
<a class="btn btn-outline-primary mx-1" href="{% url 'jobs:job_create' %}">Post a job</a>
<div class="dropdown-divider"></div>
<a class="btn btn-danger mx-1" href="{% url 'logout' %}">Logout</a>
</ul>
</div>
{% else %}
<div class="ml-auto">
<a href="{% url 'jobs:job_create' %}" class="btn btn-outline-primary ml-auto" role="button" aria-pressed="true">Post a job</a>
</div>
{% endif %}
</div>
</nav>
<div class="container">
{% for message in messages %}
@ -34,23 +58,43 @@
{{ message }}
</div>
{% endfor %}
{% block body_content %}{% endblock %}
{% block body_content %}
{% endblock %}
<footer class="pt-4 my-md-5 pt-md-5 border-top">
<div class="row">
<div class="col-12 text-center">
<small class="text-muted">&copy; {% now 'Y' %}
This service is provided to you
by <a href="https://ungleich.ch/"
target="_blank">ungleich</a>.
IPv6.work is
<small class="text-muted">&copy; {% now 'Y' %}</small>
</div>
</div>
<div class="row">
<div class="col-12 text-center">
<small>This service is provided to you
by <a href="https://ungleich.ch/" target="_blank">ungleich</a>. IPv6.work is
<a href="https://code.ungleich.ch/ungleich-public/ipv6-dot-work" target="_blank">
Open Source</a>.</small>
</div>
</div>
</footer>
<style>
.my-custom-scrollbar {
position: relative;
height: 400px;
overflow: auto;
word-break: break-all;
}
.table-wrapper-scroll-y {
display: block;
}
</style>
</div>
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>

View File

@ -1,10 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'base.html' %}
{% block body_content %}
{% load crispy_forms_tags %}
<div class="row">
<div class="col-md-12">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="h2 text-center">{{ title }}</h1>
<p class="lead"></p>
</div>
</div>
</div>
<div class="table-wrapper-scroll-y my-custom-scrollbar">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th scope="col">Conversation with {{ person }}</th>
</tr>
</thead>
<tbody>
{% for message in jobmessage %}
<tr>
<td>{{ message.sender }}: {{ message.text }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="dropdown-divider"></div>
<form action="{% url 'jobs:send_message' %}" method="POST">
{% csrf_token %}
<input type="hidden" value="{{ person.id }}" name="receiver_id">
{{ form|crispy }}
<input class="btn btn-primary btn-block" type="submit" value="Send Message">
</form>
</body>
</html>
{% endblock %}

View File

@ -11,12 +11,12 @@
<section class="bg-light-gray">
<div class="row">
{% for job in jobs %}
<div class="col-md-4 col-sm-6 portfolio-item wow fadeInUp" data-wow-delay="0.25s" style="visibility: visible; animation-delay: 0.25s; animation-name: fadeInUp;">
<div class="col-md-4 col-sm-6 portfolio-item wow fadeInUp d-flex" data-wow-delay="0.25s" style="visibility: visible; animation-delay: 0.25s; animation-name: fadeInUp;">
<div class="portfolio-caption inline-block">
<h4><a href="{{ job.get_absolute_url }}" style="color:black;">{{ job.title }}</a></h4>
<h4><a href="{{ job.get_absolute_url }}" style="color:black;">{{ job.title|truncatechars:50 }}</a></h4>
<p>&nbsp;</p>
<p class="text-muted"></p>
<p>{{job.description|truncatewords:20}}</p>
<p>{{job.description|truncatechars:120}}</p>
<p class="text-right"><a href="{% url 'jobs:job_apply' job.pk %}">Apply</a></p>
</div>
</div>

View File

@ -1,10 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{% extends 'base.html' %}
{% block body_content %}
<div class="row">
<div class="col-md-12">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="display-4">{{ title }}</h1>
<p class="lead"></p>
</div>
</div>
</div>
<div class="table-responsive">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th scope="col">Job Title</th>
<th scope="col">Date</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{% for job in jobs %}
<tr>
<td style="vertical-align: middle">{{ job.title|truncatechars:65 }}</td>
<td style="vertical-align: middle">{{ job.created.date }}</td>
<td style="vertical-align: middle">
<form action="{% url 'jobs:job_toggle' %}" method="post">{% csrf_token %}
<input type="hidden" name="job_id" value="{{ job.id }}">
{% if job.active %}
<button type="submit" class="btn btn-warning btn-block">Deactivate</button>
{% else %}
<button type="submit" class="btn btn-success btn-block">Activate</button>
{% endif %}
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
</body>
</html>

View File

@ -8,12 +8,12 @@
<div class="col-md-12">
<form method="POST" action=".">
{% csrf_token %}
{{ form |crispy }}
{{ form|crispy }}
<h4>Screening Questions</h4>
{{ question_form |crispy }}
{{ question_form|crispy }}
<a id="add_more" href="">Add More</a><br/>
<input class="btn btn-primary submit" type="submit" value="Post Job"/>
<input class="btn btn-primary btn-block my-2" type="submit" value="Post Job"/>
</form>
{{form.media}}
</div>

View File

@ -7,37 +7,24 @@
<p class="lead"></p>
</div>
</div>
<table class="table table-hover">
</div>
<div class="table-wrapper-scroll-y my-custom-scrollbar">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">First</th>
<th scope="col">Last</th>
<th scope="col">Handle</th>
<th scope="col">User</th>
<th scope="col">Last Message</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">1</th>
<td>Mark</td>
<td>Otto</td>
<td>@mdo</td>
</tr>
<tr>
<th scope="row">2</th>
<td>Jacob</td>
<td>Thornton</td>
<td>@fat</td>
</tr>
<tr>
<th scope="row">3</th>
<td colspan="2">Larry the Bird</td>
<td>@twitter</td>
{% for message in jobmessage %}
<tr onclick="window.location.href = '{{ message.sender_id }}';">
<td>{{ message.sender }}</td>
<td>{{ message.text|truncatechars:40 }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
{% for jobmessage in jobmessages %}
{% endfor %}
{% endblock %}

View File

@ -1,10 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>$Title$</title>
</head>
<body>
$END$
</body>
</html>
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block body_content %}
<div class="row">
<div class="col-md-12">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="h2 text-center">{{ title }}</h1>
<p class="lead"></p>
</div>
{% for application in applications %}
<div class="card">
<div class="card-body">
<h4 class="card-title">
<a href="{{ application.job.get_absolute_url }}">{{ application.job }}</a>
</h4>
<p class="card-text">
Submitted: {{ application.created }}
</p>
<p class="card-text">
{{ application.cover_text }}
</p>
<ol>
{% for answer in application.answers.all %}
<li>{{answer.question}}<p>{{answer.text}}</p></li>
{% endfor %}
</ol>
<button class="btn btn-outline-primary btn-block" data-toggle="modal" data-target="#messageModal">Contact Applicant</button>
<div class="modal fade" id="messageModal" tabindex="-1" role="dialog" aria-labelledby="messageModalLabel" aria-hidden="true" style="vertical-align: center">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Contact Applicant {{ application.applicant }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<form action="{% url 'jobs:send_message' %}" method="POST">{% csrf_token %}
<div class="modal-body">
<input type="hidden" value="{{ application.applicant_id }}" name="receiver_id">
{{ form|crispy }}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<input class="btn btn-primary" type="submit" value="Send Message">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -1,10 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
</body>
</html>
{% extends 'base.html' %}
{% block body_content %}
<div class="row">
<div class="col-md-12">
<div class="pricing-header px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
<h1 class="h2 text-center">{{ title }}</h1>
<p class="lead"></p>
</div>
{% for application in applications %}
<div class="card">
<div class="card-body">
<h4 class="card-title">
<a href="{{ application.job.get_absolute_url }}">{{ application.job }}</a>
</h4>
<p class="card-text">
Submitted: {{ application.created }}
</p>
<p class="card-text">
{{ application.cover_text }}
</p>
<ol>
{% for answer in application.answers.all %}
<li>{{answer.question}}<p>{{answer.text}}</p></li>
{% endfor %}
</ol>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}

View File

@ -7,22 +7,20 @@ app_name = 'jobs'
urlpatterns = [
path('', views.Index.as_view(), name='index'),
path('jobs/create/', views.JobCreate.as_view(), name='job_create'),
path('jobs/', views.JobList.as_view(), name='job_list'),
path(
'jobs/<int:pk>/detail/', views.JobDetail.as_view(), name='job_detail'),
path('jobs/me/', views.MyJobs.as_view(), name='my_jobs'),
path('jobs/<int:pk>/detail/', views.JobDetail.as_view(), name='job_detail'),
path('jobs/<int:pk>/renew/', views.JobRenew.as_view(), name='job_renew'),
path('jobs/messages/<int:pk>/', views.Conversation.as_view(), name='conversation'),
path('jobs/toggle/', views.change_status, name='job_toggle'),
path('jobs/<int:job_pk>/applications/', views.ApplicationList.as_view(), name='job_applications'),
path(
'jobs/<int:job_pk>/apply/',
views.ApplicationCreate.as_view(),
name='job_apply'),
path('jobs/applications/me/', views.ListOwnApplications.as_view(), name='my_applications'),
path('jobs/applications/', views.ListJobApplications.as_view(), name='applications_others'),
path('jobs/messages/', views.MesssageInbox.as_view(), name='messages_inbox'),
path('jobs/messages/send/', views.send_message, name='send_message'),
path('jobs/<int:job_pk>/apply/', views.ApplicationCreate.as_view(), name='job_apply'),
]
# autocomplete endpoints
urlpatterns += [
path(
'tag-autocomplete/',
autocomplete_views.TagAutocomplete.as_view(create_field='name'),
name='tag-autocomplete',
),
path('tag-autocomplete/', autocomplete_views.TagAutocomplete.as_view(create_field='name'), name='tag-autocomplete',),
]

View File

@ -1,31 +1,28 @@
from django.urls import reverse_lazy
from django.http import HttpResponseRedirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import (View, TemplateView, ListView, CreateView,
DetailView)
from django.views.generic import (View, TemplateView, ListView, CreateView, DetailView)
from django.views.generic.detail import SingleObjectMixin
from django.contrib import messages
from django.shortcuts import get_object_or_404
from django.shortcuts import get_object_or_404, redirect
from rules.contrib.views import PermissionRequiredMixin
from django.contrib.auth import get_user_model
from .models import Job, Application, Question
from .forms import JobForm, QuestionFormSet, ApplicationForm, AnswerForm
from .models import Job, Application, Question, JobMessage
from .forms import JobForm, QuestionFormSet, ApplicationForm, AnswerForm, MessageForm
User = get_user_model()
class Index(TemplateView):
template_name = 'jobs/index.html'
def get_context_data(self, *args, **kwargs):
context = super().get_context_data(*args, **kwargs)
context['jobs'] = Job.objects.all().order_by('-updated')[:6]
context['jobs'] = Job.objects.filter(active=True).order_by('-updated')[:6]
return context
class JobList(ListView):
context_object_name = 'jobs'
model = Job
class JobDetail(DetailView):
context_object_name = 'job'
model = Job
@ -88,7 +85,7 @@ class ApplicationCreate(LoginRequiredMixin, CreateView):
# TODO: restrict users from re-application
model = Application
form_class = ApplicationForm
success_url = reverse_lazy("jobs:job_list")
success_url = reverse_lazy("jobs:index")
def get_question_queryset(self):
# filter questions for particular job and order it so same queryset
@ -153,5 +150,113 @@ class ApplicationList(LoginRequiredMixin, PermissionRequiredMixin, ListView):
return super().get_queryset().filter(job_id=self.kwargs['job_pk'])
class ListOwnApplications(ListView):
model = Application
context_object_name = 'applications'
template_name = 'jobs/list_own_applications.html'
def get_queryset(self):
return Application.objects.filter(applicant=self.request.user)
def get_context_data(self, **kwargs):
context = super(ListOwnApplications, self).get_context_data(**kwargs)
context['title'] = 'Your Applications'
return context
class ListJobApplications(ListView):
model = Application
context_object_name = 'applications'
template_name = 'jobs/list_applications_others.html'
def get_queryset(self):
return Application.objects.filter(job__posted_by=self.request.user)
def get_context_data(self, **kwargs):
context = super(ListJobApplications, self).get_context_data(**kwargs)
context['title'] = 'Applications to your jobs'
context['form'] = MessageForm
return context
class ApplicationDetail(LoginRequiredMixin, DetailView):
model = Application
class MesssageInbox(LoginRequiredMixin, ListView):
model = JobMessage
context_object_name = 'jobmessage'
def get_queryset(self):
qs_list = []
qs = JobMessage.objects.filter(receiver=self.request.user).order_by('-date')
lookup = set(qs.values_list('sender', flat=True))
for sender_id in lookup:
for elem in qs:
if elem.sender_id == sender_id:
qs_list.append(elem.id)
break
return JobMessage.objects.filter(pk__in=qs_list).order_by('-date')
def get_context_data(self, **kwargs):
context = super(MesssageInbox, self).get_context_data(**kwargs)
context['title'] = 'Messages Inbox'
return context
class Conversation(LoginRequiredMixin, ListView):
model = JobMessage
context_object_name = 'jobmessage'
template_name = 'jobs/conversation.html'
def get_queryset(self):
queryset1 = JobMessage.objects.filter(
receiver=self.request.user, sender=self.kwargs['pk'])
queryset2 = JobMessage.objects.filter(
receiver=self.kwargs['pk'], sender=self.request.user)
return queryset1.union(queryset2).order_by('date')
def get_context_data(self, **kwargs):
context = super(Conversation, self).get_context_data(**kwargs)
context['form'] = MessageForm
context['person'] = User.objects.get(id=self.kwargs['pk'])
return context
class MyJobs(LoginRequiredMixin, ListView):
model = Job
context_object_name = 'jobs'
template_name = 'jobs/job.html'
def get_queryset(self):
return Job.objects.filter(posted_by=self.request.user).order_by('expires')
def get_context_data(self, **kwargs):
context = super(MyJobs, self).get_context_data(**kwargs)
context['title'] = 'Your jobs'
return context
def send_message(request):
form = MessageForm(request.POST)
if form.is_valid():
user = User.objects.get(id=request.POST.get('receiver_id'))
JobMessage.objects.create(sender=request.user, receiver=user, text=request.POST.get('text'))
else:
print("Error")
return redirect('jobs:conversation', pk=request.POST.get('receiver_id'))
def change_status(request):
print(request.POST)
job_id = request.POST.get('job_id')
if job_id is not None:
job = Job.objects.get(id=job_id)
if job.active:
job.active = False
else:
job.active = True
job.save()
else:
print("Error")
return redirect('jobs:my_jobs')

View File

@ -1,10 +1,18 @@
{% extends 'base.html' %}
{% load crispy_forms_tags %}
{% block title %}Login{% endblock %}
{% block body_content %}
<h2>Login</h2>
<form method="post">
{% csrf_token %} {{ form.as_p }}
<button type="submit">Login</button>
</form>
Don't have an account? Sign up <a href="{% url 'signup' %}">here</a>.
<div class="row justify-content-md-center">
<div class="col-lg-6">
<h1 class="my-3 text-center">Login</h1>
<form method="post">
{% csrf_token %} {{ form|crispy }}
<button class="btn btn-primary btn-block" type="submit">Login</button>
</form>
<div class="mt-4 mb-2">
Don't have an account? Sign up <a href="{% url 'signup' %}">here</a>.
</div>
</div>
</div>
{% endblock %}