Entities BaseListView

Criado uma ListView base e utilizada como padrão para exibir os registros cadastrados
This commit is contained in:
Eberton committed 2026-05-23 19:20:14 -03:00
1 parent 24650484f1
commit d3883669f3
31 files changed
+661 -515

No files matched your search

+1
View File
@@ -0,0 +1 @@
404 not found
@@ -1,48 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'css/layout/entity_home.css' %}">
{% endblock %}
{% block content %}
<div class="entity_title">
<h1>{{entity_name}}</h1>
<form method="GET" action="{{entity_home_url}}" class="form_entity_search">
{% if filter_query %}
<a href="{{entity_home_url}}" class="btn-clear-filter">{{filter_query}}<i class="fa-solid fa-xmark"></i></a>
{% endif %}
<label for="entity_search">Buscar</label>
<input type="text" name="entity_search" required>
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
</form>
<a href="{{entity_new_url}}">Novo <i class="fa-solid fa-plus"></i></a>
</div>
{% if not entities %}
<p>Nenhum registro encontrado.</p>
{% else %}
<table>
<tr>
<th>Identificador</th>
<th>Idade</th>
<th>Peso</th>
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for entity in entities %}
<tr>
<td>{{ animal.identifier }}</td>
<td>{{ animal.age }}</td>
<td>{{ animal.weight }} Kg</td>
<td><a href="{% url 'entities:animal:animal_update' animal.id %}"><i class="fa-solid fa-pen-to-square"></i></a></td>
<td>
<form method="POST" action="{% url 'entities:animal:animal_delete' animal.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash"><i class="fa-solid fa-trash"></i></button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
@@ -10,7 +10,7 @@
<form method="post" action="{% url 'entities:user:farm_set_post' %}" class="navbar-farm-selector w33">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.get_full_path }}">
<input type="hidden" name="next" value="{{ request.path }}">
<select name="farm_id" onchange="this.form.submit()">
{% for farm in farms %}
<option value="{{ farm.id }}" {% if farm.id == SELECTED_FARM.id %}selected{% endif %}>
@@ -12,32 +12,32 @@
<li class="sidebar-header">REGISTROS</li>
<li class="{% if request.resolver_match.url_name not in 'rotation_home, rotation_new, rotation_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:rotation:rotation_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'rotation_home, rotation_new, rotation_delete' %}active{% endif %}"><i class="fa-solid fa-arrows-spin"></i> Movimentações</a>
<li class="{% if request.resolver_match.url_name not in 'rotation_home, rotation_create, rotation_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:rotation:rotation_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'rotation_home, rotation_create, rotation_delete' %}active{% endif %}"><i class="fa-solid fa-arrows-spin"></i> Movimentações</a>
</li>
<li class="sidebar-header">CADASTROS</li>
<li class="{% if request.resolver_match.url_name not in 'animal_home, animal_new, animal_update, animal_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:animal:animal_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'animal_home, animal_new, animal_update, animal_delete' %}active{% endif %}"><i class="fa-solid fa-cow"></i> Animais</a>
<li class="{% if request.resolver_match.url_name not in 'animal_list, animal_create, animal_update, animal_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:animal:animal_list' %}" class="sidebar-link {% if request.resolver_match.url_name in 'animal_list, animal_create, animal_update, animal_delete' %}active{% endif %}"><i class="fa-solid fa-cow"></i> Animais</a>
</li>
<li class="{% if request.resolver_match.url_name not in 'batch_home, batch_new, batch_update, batch_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:animal:batch_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'batch_home, batch_new, batch_update, batch_delete' %}active{% endif %}"><i class="fa-solid fa-layer-group"></i> Lotes</a>
<li class="{% if request.resolver_match.url_name not in 'batch_list, batch_create, batch_update, batch_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:animal:batch_list' %}" class="sidebar-link {% if request.resolver_match.url_name in 'batch_list, batch_create, batch_update, batch_delete' %}active{% endif %}"><i class="fa-solid fa-layer-group"></i> Lotes</a>
</li>
<li class="{% if request.resolver_match.url_name not in 'paddock_home, paddock_new, paddock_update, paddock_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:paddock_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'paddock_home, paddock_new, paddock_update, paddock_delete' %}active{% endif %}"><i class="fa-solid fa-leaf"></i> Piquetes</a>
<li class="{% if request.resolver_match.url_name not in 'paddock_list, paddock_create, paddock_update, paddock_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:paddock_list' %}" class="sidebar-link {% if request.resolver_match.url_name in 'paddock_list, paddock_create, paddock_update, paddock_delete' %}active{% endif %}"><i class="fa-solid fa-leaf"></i> Piquetes</a>
</li>
<li class="{% if request.resolver_match.url_name not in 'paddock_group_home, paddock_group_new, paddock_group_update, paddock_group_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:paddock_group_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'paddock_group_home, paddock_group_new, paddock_group_update, paddock_group_delete' %}active{% endif %}"><i class="fa-solid fa-layer-group"></i> Blocos</a>
<li class="{% if request.resolver_match.url_name not in 'paddock_group_list, paddock_group_create, paddock_group_update, paddock_group_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:paddock_group_list' %}" class="sidebar-link {% if request.resolver_match.url_name in 'paddock_group_list, paddock_group_create, paddock_group_update, paddock_group_delete' %}active{% endif %}"><i class="fa-solid fa-layer-group"></i> Blocos</a>
</li>
<li class="sidebar-header">CONFIGURAÇÔES</li>
<li class="{% if request.resolver_match.url_name not in 'farm_home, farm_new, farm_update, farm_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:farm_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'farm_home, farm_new, farm_update, farm_delete' %}active{% endif %}"><i class="fa-solid fa-gear"></i> Fazenda</a>
<li class="{% if request.resolver_match.url_name not in 'farm_home, farm_create, farm_update, farm_delete' %}highlight-hover{% endif %}">
<a href="{% url 'entities:farm:farm_home' %}" class="sidebar-link {% if request.resolver_match.url_name in 'farm_home, farm_create, farm_update, farm_delete' %}active{% endif %}"><i class="fa-solid fa-gear"></i> Fazenda</a>
</li>
{% if user.is_staff or user.is_superuser %}
<li class="{% if request.resolver_match.url_name not in 'admin' %}highlight-hover{% endif %}">
@@ -0,0 +1,22 @@
from django import template
register = template.Library()
"""
Allows dinamic access to object attributes in templates.
Exemple:
{{ obj|get_attr:"name" }}
"""
@register.filter
def get_attr(obj, attr):
try:
value = getattr(obj, attr)
if callable(value):
value = value()
# If none, return empty string to avoid displaying "None" in the template
return value if value is not None else ""
except Exception:
return ""
+11 -64
View File
@@ -12,39 +12,14 @@ def get_selected_farm(request):
# Animal Services
def get_animal_home_context(request):
selected_farm = get_selected_farm(request)
context = {}
context['title'] = 'Animais'
context['farm'] = selected_farm
animals = Animal.objects.filter(
farm=selected_farm,
is_active=True
)
filter_query = request.GET.get('animal_search')
if filter_query:
context['filter_query'] = filter_query
animals = animals.filter(
identifier__icontains=filter_query
)
context['animals'] = animals
return context
def get_animal_new_context():
form = AnimalCreateForm()
context = {}
context['title'] = 'Novo animal'
context['form'] = form
context['action_url'] = reverse('entities:animal:animal_new')
context['return_url'] = reverse('entities:animal:animal_home')
context['action_url'] = reverse('entities:animal:animal_create')
context['return_url'] = reverse('entities:animal:animal_list')
return context
@@ -72,7 +47,7 @@ def update_animal(request, animal_id):
'entities:animal:animal_update',
kwargs={'animal_id': animal.pk}
)
context['return_url'] = reverse('entities:animal:animal_home')
context['return_url'] = reverse('entities:animal:animal_list')
if request.method == 'GET':
form = AnimalEditForm(instance=animal)
@@ -82,7 +57,7 @@ def update_animal(request, animal_id):
if form.is_valid():
form.save()
return {'redirect': 'entities:animal:animal_home'}
return {'redirect': 'entities:animal:animal_list'}
context['form'] = form
@@ -97,34 +72,6 @@ def delete_animal(request, animal_id):
# Batch Services
def get_batch_home_context(request):
selected_farm = get_selected_farm(request)
context = {}
context['title'] = 'Lotes'
context['farm'] = selected_farm
all_batches = Batch.objects.filter(
batchanimal__animal__farm=selected_farm,
is_active=True
).distinct().prefetch_related(
Prefetch('animals', queryset=Animal.objects.filter(is_active=True))
)
filter_query = request.GET.get('batch_search')
if filter_query:
context['filter_query'] = filter_query
all_batches = all_batches.filter(
identifier__icontains=filter_query
)
context['batches'] = all_batches
return context
def create_batch(request):
selected_farm = get_selected_farm(request)
@@ -138,8 +85,8 @@ def create_batch(request):
context = {
'title': 'Novo Lote',
'form': form,
'action_url': reverse('entities:animal:batch_new'),
'return_url': reverse('entities:animal:batch_home')
'action_url': reverse('entities:animal:batch_create'),
'return_url': reverse('entities:animal:batch_list')
}
return {'context': context}
@@ -152,7 +99,7 @@ def create_batch(request):
batch.save()
form.save_m2m()
return {'redirect': 'entities:animal:batch_home'}
return {'redirect': 'entities:animal:batch_list'}
form.fields['animals'].queryset = Animal.objects.filter(
farm=selected_farm,
@@ -162,8 +109,8 @@ def create_batch(request):
context = {
'title': 'Novo Lote',
'form': form,
'action_url': reverse('entities:animal:batch_new'),
'return_url': reverse('entities:animal:batch_home')
'action_url': reverse('entities:animal:batch_create'),
'return_url': reverse('entities:animal:batch_list')
}
return {'context': context}
@@ -175,7 +122,7 @@ def update_batch(request, batch_id):
context = {
'title': f'Editar Lote {batch_old.identifier}',
'return_url': reverse('entities:animal:batch_home')
'return_url': reverse('entities:animal:batch_list')
}
if request.method == 'GET':
@@ -204,7 +151,7 @@ def update_batch(request, batch_id):
batch_new.save()
form.save_m2m()
return {'redirect': 'entities:animal:batch_home'}
return {'redirect': 'entities:animal:batch_list'}
form.fields['animals'].queryset = Animal.objects.filter(
farm=selected_farm,
+16 -70
View File
@@ -33,8 +33,8 @@ def get_farm_new_context():
context = {}
context['title'] = 'Nova fazenda'
context['form'] = form
context['action_url'] = reverse('entities:farm:farm_new')
context['return_url'] = reverse('entities:farm:farm_home')
context['action_url'] = reverse('entities:farm:farm_create')
context['return_url'] = reverse('entities:farm:farm_list')
return context
@@ -60,7 +60,7 @@ def update_farm(request, farm_id):
'entities:farm:farm_update',
kwargs={'farm_id': farm.pk}
)
context['return_url'] = reverse('entities:farm:farm_home')
context['return_url'] = reverse('entities:farm:farm_list')
if request.method == 'GET':
form = FarmEditForm(instance=farm)
@@ -70,7 +70,7 @@ def update_farm(request, farm_id):
if form.is_valid():
form.save()
return {'redirect': 'entities:farm:farm_home'}
return {'redirect': 'entities:farm:farm_list'}
context['form'] = form
@@ -84,32 +84,6 @@ def delete_farm(farm_id):
# Paddock Services
def get_paddock_home_context(request):
selected_farm = get_selected_farm(request)
context = {}
context['title'] = 'Piquete'
context['farm'] = selected_farm
all_paddocks = Paddock.objects.filter(
farm=selected_farm,
is_active=True
)
filter_query = request.GET.get('paddock_search')
if filter_query:
context['filter_query'] = filter_query
all_paddocks = all_paddocks.filter(
identifier__icontains=filter_query
)
context['paddocks'] = all_paddocks
return context
def create_paddock(request):
selected_farm = get_selected_farm(request)
@@ -119,8 +93,8 @@ def create_paddock(request):
context = {
'title': 'Novo Piquete',
'form': form,
'action_url': reverse('entities:farm:paddock_new'),
'return_url': reverse('entities:farm:paddock_home')
'action_url': reverse('entities:farm:paddock_create'),
'return_url': reverse('entities:farm:paddock_list')
}
return {'context': context}
@@ -133,13 +107,13 @@ def create_paddock(request):
paddock.farm = selected_farm
paddock.save()
return {'redirect': 'entities:farm:paddock_home'}
return {'redirect': 'entities:farm:paddock_list'}
context = {
'title': 'Novo Piquete',
'form': form,
'action_url': reverse('entities:farm:paddock_new'),
'return_url': reverse('entities:farm:paddock_home')
'action_url': reverse('entities:farm:paddock_create'),
'return_url': reverse('entities:farm:paddock_list')
}
return {'context': context}
@@ -155,7 +129,7 @@ def update_paddock(request, paddock_id):
'entities:farm:paddock_update',
kwargs={'paddock_id': paddock.pk}
)
context['return_url'] = reverse('entities:farm:paddock_home')
context['return_url'] = reverse('entities:farm:paddock_list')
if request.method == 'GET':
form = PaddockEditForm(instance=paddock)
@@ -165,7 +139,7 @@ def update_paddock(request, paddock_id):
if form.is_valid():
form.save()
return {'redirect': 'entities:farm:paddock_home'}
return {'redirect': 'entities:farm:paddock_list'}
context['form'] = form
@@ -179,34 +153,6 @@ def delete_paddock(paddock_id):
# Paddock Group Services
def get_paddock_group_home_context(request):
selected_farm = get_selected_farm(request)
context = {}
context['title'] = 'Blocos'
context['farm'] = selected_farm
all_paddock_groups = PaddockGroup.objects.filter(
paddocks__farm=selected_farm,
is_active=True
).distinct().prefetch_related(
Prefetch('paddocks', queryset=Paddock.objects.filter(is_active=True))
)
filter_query = request.GET.get('paddock_group_search')
if filter_query:
context['filter_query'] = filter_query
all_paddock_groups = all_paddock_groups.filter(
identifier__icontains=filter_query
)
context['paddock_groups'] = all_paddock_groups
return context
def create_paddock_group(request):
selected_farm = get_selected_farm(request)
@@ -224,7 +170,7 @@ def create_paddock_group(request):
paddock_group = form.save(commit=False)
paddock_group.save()
return {'redirect': 'entities:farm:paddock_group_home'}
return {'redirect': 'entities:farm:paddock_group_list'}
form.fields['paddocks'].queryset = Paddock.objects.filter(
farm=selected_farm,
@@ -234,8 +180,8 @@ def create_paddock_group(request):
context = {
'title': 'Novo Bloco',
'form': form,
'action_url': reverse('entities:farm:paddock_group_new'),
'return_url': reverse('entities:farm:paddock_group_home')
'action_url': reverse('entities:farm:paddock_group_create'),
'return_url': reverse('entities:farm:paddock_group_list')
}
return {'context': context}
@@ -247,7 +193,7 @@ def update_paddock_group(request, paddock_group_id):
context = {
'title': f'Editar Lote {paddock_group.identifier}',
'return_url': reverse('entities:farm:paddock_group_home')
'return_url': reverse('entities:farm:paddock_group_list')
}
if request.method == 'GET':
@@ -270,7 +216,7 @@ def update_paddock_group(request, paddock_group_id):
paddock_group = form.save()
paddock_group.save()
return {'redirect': 'entities:farm:paddock_group_home'}
return {'redirect': 'entities:farm:paddock_group_list'}
form.fields['paddocks'].queryset = Paddock.objects.filter(
farm=selected_farm,
@@ -0,0 +1,208 @@
/* PAGE HEADER */
.entity_search {
display: flex;
align-items: center;
gap: var(--space-md);
margin-bottom: var(--space-lg);
margin-top: var(--space-lg);
}
.entity_search h1 {
font-size: var(--font-xl);
color: var(--color-text-dark);
margin: 0;
margin-right: var(--space-lg);
}
/* SEARCH FORM */
.entity_search form {
display: flex;
align-items: center;
gap: var(--space-xs);
margin-left: auto;
}
.entity_search form label {
font-size: var(--font-sm);
color: var(--color-text-dark);
}
/* Input com altura igual ao botão */
.entity_search form input {
height: 32px;
padding: 0 var(--space-sm);
font-size: var(--font-sm);
border: 1px solid var(--color-border-subtle);
border-radius: var(--border-radius-sm);
outline: none;
}
.entity_search form input:focus {
border-color: var(--color-primary);
}
/* Botão buscar */
.entity_search form button {
height: 32px;
padding: 0 var(--space-sm);
border: none;
border-radius: var(--border-radius-sm);
background: var(--color-primary);
color: var(--color-text-light);
cursor: pointer;
transition: background var(--transition-speed);
}
.entity_search form button:hover {
background: var(--color-secondary);
}
/* CLEAR FILTER */
.btn-clear-filter {
display: flex;
align-items: center;
gap: 4px;
height: 32px;
padding: 0 var(--space-sm);
background: var(--color-danger-light);
color: var(--color-danger);
border-radius: var(--border-radius-sm);
text-decoration: none;
font-size: var(--font-sm);
}
.btn-clear-filter:hover {
background: var(--color-danger);
color: var(--color-text-light);
}
/* NEW BUTTON */
.entity_search > a {
height: 32px;
padding: 0 var(--space-md);
background: var(--color-success);
color: var(--color-text-light);
border-radius: var(--border-radius-sm);
text-decoration: none;
font-size: var(--font-sm);
display: flex;
align-items: center;
gap: var(--space-xs);
transition: background var(--transition-speed);
}
.entity_search > a:hover {
background: var(--color-success);
opacity: 0.85;
}
/* TABLE */
table {
width: 100%;
border-collapse: collapse;
background: var(--color-bg-white);
border-radius: var(--border-radius-sm);
overflow: hidden;
}
table th {
text-align: left;
padding: var(--space-xs) var(--space-sm);
font-size: var(--font-md);
background: var(--color-bg-dark);
color: var(--color-text-light);
font-weight: 500;
}
table td {
padding: var(--space-xs);
font-size: var(--font-md);
border-bottom: 1px solid var(--color-border-subtle);
}
table tr:last-child td {
border-bottom: none;
}
table tbody tr:hover {
background: var(--color-bg-light);
}
/* Fixed width on the last two colluns */
td:last-child,
th:last-child {
width: 60px;
text-align: center;
}
td:nth-last-child(2),
th:nth-last-child(2) {
width: 60px;
text-align: center;
}
td, th {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* ACTION ICONS */
table a {
color: var(--color-primary);
transition: color var(--transition-speed);
}
table a:hover {
color: var(--color-secondary);
}
/* DELETE BUTTON */
.btn-icon-trash {
background: transparent;
border: none;
color: var(--color-danger);
cursor: pointer;
transition: color var(--transition-speed);
}
.btn-icon-trash:hover {
color: var(--color-danger);
opacity: 0.7;
}
/* PAGINATION */
.pagination {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
margin-top: var(--space-lg);
font-size: var(--font-sm);
}
.pagination .left {
justify-self: end;
display: flex;
gap: var(--space-xs);
}
.pagination .center {
justify-self: center;
white-space: nowrap;
margin: 0 10px;
}
.pagination .right {
justify-self: start;
display: flex;
gap: var(--space-xs);
}
.pagination a {
padding: 3px 7px;
background: var(--color-bg-light);
border-radius: var(--border-radius-sm);
text-decoration: none;
color: var(--color-text-dark);
transition: all var(--transition-speed);
}
@@ -0,0 +1,6 @@
<div class="active_rotaiton_card">
<h4>Movimentação {rotation.id}</h4>
<p>Lote {lote.name} atualmente no piquete {paddock.number} a {rotation.duration} atrás</p>
<p>var = {{var}}</p>
<a href="#vermais">Ver mais</a>
</div>
@@ -1,48 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'animal/css/animal.css' %}">
{% endblock %}
{% block content %}
<div class="animal_title">
<h1>Animais</h1>
<form method="GET" action="{% url 'entities:animal:animal_home' %}" class="form_animal_search">
{% if filter_query %}
<a href="{% url 'entities:animal:animal_home' %}" class="btn-clear-filter">{{filter_query}}<i class="fa-solid fa-xmark"></i></a>
{% endif %}
<label for="animal_search">Buscar</label>
<input type="text" name="animal_search" required>
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
</form>
<a href="{% url 'entities:animal:animal_new' %}">Novo <i class="fa-solid fa-plus"></i></a>
</div>
{% if not animals %}
<p>Nenhum animal encontrado.</p>
{% else %}
<table>
<tr>
<th>Identificador</th>
<th>Idade</th>
<th>Peso</th>
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for animal in animals %}
<tr>
<td>{{ animal.identifier }}</td>
<td>{{ animal.age }}</td>
<td>{{ animal.weight }} Kg</td>
<td><a href="{% url 'entities:animal:animal_update' animal.id %}"><i class="fa-solid fa-pen-to-square"></i></a></td>
<td>
<form method="POST" action="{% url 'entities:animal:animal_delete' animal.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash"><i class="fa-solid fa-trash"></i></button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
@@ -1,48 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'animal/css/batch.css' %}">
{% endblock %}
{% block content %}
<div class="batch_title">
<h1>Lotes</h1>
<form method="GET" action="{% url 'entities:animal:batch_home' %}" class="form_batch_search">
{% if filter_query %}
<a href="{% url 'entities:animal:batch_home' %}" class="btn-clear-filter">{{filter_query}}<i class="fa-solid fa-xmark"></i></a>
{% endif %}
<label for="batch_search">Buscar</label>
<input type="text" name="batch_search" required>
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
</form>
<a href="{% url 'entities:animal:batch_new' %}">Novo <i class="fa-solid fa-plus"></i></a>
</div>
{% if not batches %}
<p>Nenhum lote encontrado.</p>
{% else %}
<table>
<tr>
<th>Identificador</th>
<th>Categoria animal</th>
<th>Tipo de lote</th>
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for batch in batches %}
<tr>
<td>{{ batch.identifier }}</td>
<td>{{ batch.animal_category }}</td>
<td>{{ batch.batch_type }} Kg</td>
<td><a href="{% url 'entities:animal:batch_update' batch.id %}"><i class="fa-solid fa-pen-to-square"></i></a></td>
<td>
<form method="POST" action="{% url 'entities:animal:batch_delete' batch.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash"><i class="fa-solid fa-trash"></i></button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
@@ -0,0 +1,106 @@
{% extends 'base.html' %}
{% load static %}
{% load custom_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'layout/entity_list.css' %}">
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% block before_table %} {% endblock %}
<div class="entity_search">
<p>Busca geral</p>
<!-- Search -->
<form method="GET" action="{% url list_url %}">
{% if query %}
<a href="{% url list_url %}" class="btn-clear-filter">
{{ query }} <i class="fa-solid fa-xmark"></i>
</a>
{% endif %}
<label for="search">Buscar</label>
<input type="text" name="q" value="{{ query }}" required>
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
</form>
<!-- New -->
<a href="{% url create_url %}">
Novo <i class="fa-solid fa-plus"></i>
</a>
</div>
{% if not object_list %}
<p>Nenhum registro encontrado.</p>
{% else %}
{% block table %}
<table>
<tr>
{% for col in columns %}
<th>{{ col.label }}</th>
{% endfor %}
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for obj in object_list %}
<tr>
{% for col in columns %}
<td>
{{ obj|get_attr:col.field }}
</td>
{% endfor %}
<!-- Update -->
<td>
<a href="{% url edit_url obj.id %}">
<i class="fa-solid fa-pen-to-square"></i>
</a>
</td>
<!-- Delete -->
<td>
<form method="POST" action="{% url delete_url obj.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash">
<i class="fa-solid fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
<!-- Pagination -->
{% if is_paginated %}
<div class="pagination">
<div class="left">
{% if page_obj.has_previous %}
<a href="?page=1&q={{ query }}"><i class="fa-solid fa-angles-left"></i></a>
<a href="?page={{ page_obj.previous_page_number }}&q={{ query }}"><i class="fa-solid fa-angle-left"></i></a>
{% endif %}
</div>
<span class="center">
Página {{ page_obj.number }} de {{ page_obj.paginator.num_pages }}
</span>
<div class="right">
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}&q={{ query }}"><i class="fa-solid fa-angle-right"></i></a>
<a href="?page={{ page_obj.paginator.num_pages }}&q={{ query }}"><i class="fa-solid fa-angles-right"></i></a>
{% endif %}
</div>
</div>
{% endif %}
{% endif %}
{% block after_table %} {% endblock %}
{% endblock %}
@@ -16,7 +16,7 @@
<input type="text" name="farm_search" required>
<button type="submit"><i class="fa-solid fa-magnifying-glass"></i></button>
</form>
<a href="{% url 'entities:farm:farm_new' %}">Novo <i class="fa-solid fa-plus"></i></a>
<a href="{% url 'entities:farm:farm_create' %}">Novo <i class="fa-solid fa-plus"></i></a>
</div>
{% if not farms %}
<p>Nenhuma fazenda encontrado.</p>
@@ -1,85 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'farm/css/paddock.css' %}">
{% endblock %}
{% block content %}
<div class="paddock_title">
<h1>Piquetes</h1>
<form method="GET" action="{% url 'entities:farm:paddock_home' %}" class="form_paddock_search">
{% if filter_query %}
<a href="{% url 'entities:farm:paddock_home' %}" class="btn-clear-filter">
{{filter_query}}<i class="fa-solid fa-xmark"></i>
</a>
{% endif %}
<label for="paddock_search">Buscar</label>
<input type="text" name="paddock_search" required>
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
</form>
<a href="{% url 'entities:farm:paddock_new' %}">
Novo <i class="fa-solid fa-plus"></i>
</a>
</div>
{% if not paddocks %}
<p>Nenhum piquete encontrado.</p>
{% else %}
<table>
<tr>
<th>Número</th>
<th>Área</th>
<th>Capacidade</th>
<th>Ocupado</th>
<th>Bloco</th>
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for paddock in paddocks %}
<tr>
<td>{{ paddock.paddock_number }}</td>
<td>{{ paddock.area }} ha</td>
<td>{{ paddock.load_capacity }}</td>
<td>
{% if paddock.occupied %}
Sim
{% else %}
Não
{% endif %}
</td>
<td>
{% if paddock.paddock_group %}
{{ paddock.paddock_group.identifier }}
{% else %}
-
{% endif %}
</td>
<td>
<a href="{% url 'entities:farm:paddock_update' paddock.id %}">
<i class="fa-solid fa-pen-to-square"></i>
</a>
</td>
<td>
<form method="POST" action="{% url 'entities:farm:paddock_delete' paddock.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash">
<i class="fa-solid fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
@@ -1,76 +0,0 @@
{% extends 'base.html' %}
{% load static %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'farm/css/paddock_group.css' %}">
{% endblock %}
{% block content %}
<div class="paddock_group_title">
<h1>Blocos</h1>
<form method="GET" action="{% url 'entities:farm:paddock_group_home' %}" class="form_paddock_group_search">
{% if filter_query %}
<a href="{% url 'entities:farm:paddock_group_home' %}" class="btn-clear-filter">
{{filter_query}}<i class="fa-solid fa-xmark"></i>
</a>
{% endif %}
<label for="paddock_group_search">Buscar</label>
<input type="text" name="paddock_group_search" required>
<button type="submit">
<i class="fa-solid fa-magnifying-glass"></i>
</button>
</form>
<a href="{% url 'entities:farm:paddock_group_new' %}">
Novo <i class="fa-solid fa-plus"></i>
</a>
</div>
{% if not paddock_groups %}
<p>Nenhum bloco encontrado.</p>
{% else %}
<table>
<tr>
<th>Identificador</th>
<th>Descrição</th>
<th>Piquetes</th>
<th>Editar</th>
<th>Excluir</th>
</tr>
{% for group in paddock_groups %}
<tr>
<td>{{ group.identifier }}</td>
<td>{{ group.description }}</td>
<td>
{% for paddock in group.paddocks.all %}
{{ paddock.paddock_number }}{% if not forloop.last %}, {% endif %}
{% empty %}
-
{% endfor %}
</td>
<td>
<a href="{% url 'entities:farm:paddock_group_update' group.id %}">
<i class="fa-solid fa-pen-to-square"></i>
</a>
</td>
<td>
<form method="POST" action="{% url 'entities:farm:paddock_group_delete' group.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash">
<i class="fa-solid fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endblock %}
@@ -1,10 +1,37 @@
{% extends 'base.html' %}
{% load static %}
{% extends 'entity_list.html' %}
{% load custom_tags %}
{% block extra_css %}
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
{% block before_table %}
<p>Essas informações aparecem antes da tabela apenas para a tela de movimentações</p>
{% endblock %}
{% block content %}
Home - Rotation
{% endblock %}
{% block table %}
<table>
<tr>
{% for col in columns %}
<th>{{ col.label }}</th>
{% endfor %}
<th>Excluir</th>
</tr>
{% for obj in object_list %}
<tr>
{% for col in columns %}
<td>
{{ obj|get_attr:col.field }}
</td>
{% endfor %}
<!-- Delete -->
<td>
<form method="POST" action="{% url delete_url obj.id %}">
{% csrf_token %}
<button type="submit" class="btn-icon-trash">
<i class="fa-solid fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</table>
{% endblock %}
+5 -4
View File
@@ -1,17 +1,18 @@
from django.urls import path
from ..views import animal as views
from ..views import AnimalListView, BatchListView
app_name = 'animal'
urlpatterns = [
path('', views.animal_home, name='animal_home'),
path('batch/', views.batch_home, name='batch_home'),
path('list/', AnimalListView.as_view(), name='animal_list'),
path('batch/list/', BatchListView.as_view(), name='batch_list'),
path('new/', views.animal_new, name='animal_new'),
path('new/', views.animal_new, name='animal_create'),
path('update/<int:animal_id>', views.animal_update, name='animal_update'),
path('delete/<int:animal_id>', views.animal_delete, name='animal_delete'),
path('batch/new/', views.batch_new, name='batch_new'),
path('batch/new/', views.batch_new, name='batch_create'),
path('batch/update/<int:batch_id>', views.batch_update, name='batch_update'),
path('batch/delete/<int:batch_id>', views.batch_delete, name='batch_delete'),
]
+6 -5
View File
@@ -1,22 +1,23 @@
from django.urls import path
from ..views import farm as views
from ..views import PaddockListView, PaddockGroupListView
app_name = 'farm'
urlpatterns = [
path('', views.farm_home, name='farm_home'),
path('paddock/', views.paddock_home, name='paddock_home'),
path("paddock/group/", views.paddock_group_home, name="paddock_group_home"),
path('paddock/list/', PaddockListView.as_view(), name='paddock_list'),
path('paddock/group/list/', PaddockGroupListView.as_view(), name='paddock_group_list'),
path('new/', views.farm_new, name='farm_new'),
path('new/', views.farm_new, name='farm_create'),
path('update/<int:farm_id>', views.farm_update, name='farm_update'),
path('delete/<int:farm_id>', views.farm_delete, name='farm_delete'),
path('paddock/new/', views.paddock_new, name='paddock_new'),
path('paddock/new/', views.paddock_new, name='paddock_create'),
path('paddock/update/<int:paddock_id>', views.paddock_update, name='paddock_update'),
path('paddock/delete/<int:paddock_id>', views.paddock_delete, name='paddock_delete'),
path('paddock_group/new/', views.paddock_group_new, name='paddock_group_new'),
path('paddock_group/new/', views.paddock_group_new, name='paddock_group_create'),
path('paddock_group/update/<int:paddock_group_id>', views.paddock_group_update, name='paddock_group_update'),
path('paddock_group/delete/<int:paddock_group_id>', views.paddock_group_delete, name='paddock_group_delete'),
]
+3 -4
View File
@@ -1,14 +1,13 @@
from django.urls import path
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from ..views import RotationListView
from ..views import rotation as views
app_name = 'rotation'
urlpatterns = [
path('', views.rotation_home, name='rotation_home'),
path('', RotationListView.as_view(), name='rotation_home'),
path('new/', views.rotation_new, name='rotation_new'),
path('new/', views.rotation_create, name='rotation_create'),
path('delete/<int:rotation_id>', views.rotation_delete, name='rotation_delete'),
]
+8 -1
View File
@@ -1,3 +1,10 @@
from .animal import *
from .farm import *
from .rotation import *
from .rotation import *
from .list.base import BaseListView
from .list.animal import AnimalListView
from .list.batch import BatchListView
from .list.paddock import PaddockListView
from .list.paddock_group import PaddockGroupListView
from .list.rotation import RotationListView
+4 -16
View File
@@ -5,12 +5,6 @@ from core.decorators import farm_required
from ..services import animal as services
# Animal Views
@farm_required
def animal_home(request):
context = services.get_animal_home_context(request)
return render(request, 'animal/animal_home.html', context)
@farm_required
def animal_new(request):
if request.method == 'GET':
@@ -20,9 +14,9 @@ def animal_new(request):
elif request.method == 'POST':
success = services.create_animal(request)
if success:
return redirect('entities:animal:animal_home')
return redirect('entities:animal:animal_list')
return redirect('entities:animal:animal_new')
return redirect('entities:animal:animal_create')
@farm_required
@@ -39,16 +33,10 @@ def animal_update(request, animal_id):
@require_http_methods(["POST"])
def animal_delete(request, animal_id):
services.delete_animal(request, animal_id)
return redirect('entities:animal:animal_home')
return redirect('entities:animal:animal_list')
# Batch Views
@farm_required
def batch_home(request):
context = services.get_batch_home_context(request)
return render(request, 'batch/batch_home.html', context)
@farm_required
def batch_new(request):
result = services.create_batch(request)
@@ -73,4 +61,4 @@ def batch_update(request, batch_id):
@require_http_methods(["POST"])
def batch_delete(request, batch_id):
services.delete_batch(batch_id)
return redirect('entities:animal:batch_home')
return redirect('entities:animal:batch_list')
+9 -23
View File
@@ -19,9 +19,9 @@ def farm_new(request):
elif request.method == 'POST':
success = services.create_farm(request)
if success:
return redirect('entities:farm:farm_home')
return redirect('entities:farm:farm_list')
return redirect('entities:farm:farm_new')
return redirect('entities:farm:farm_create')
@@ -39,17 +39,10 @@ def farm_update(request, farm_id):
@require_http_methods(["POST"])
def farm_delete(request, farm_id):
services.delete_farm(request, farm_id)
return redirect('entities:farm:farm_home')
return redirect('entities:farm:farm_create')
# Paddock Views
@farm_required
def paddock_home(request):
context = {}
context['title'] = 'Piquete'
return render(request, 'paddock/paddock_home.html', context)
@farm_required
def paddock_new(request):
if request.method == 'GET':
@@ -59,9 +52,9 @@ def paddock_new(request):
elif request.method == 'POST':
success = services.create_paddock(request)
if success:
return redirect('entities:farm:paddock_home')
return redirect('entities:farm:paddock_list')
return redirect('entities:farm:paddock_new')
return redirect('entities:farm:paddock_create')
@@ -79,17 +72,10 @@ def paddock_update(request, paddock_id):
@require_http_methods(["POST"])
def paddock_delete(request, paddock_id):
services.delete_paddock(request, paddock_id)
return redirect('entities:farm:paddock_home')
return redirect('entities:farm:paddock_list')
# Paddock Group Views
@farm_required
def paddock_group_home(request):
context = {}
context['title'] = 'Bloco'
return render(request, 'paddock_group/paddock_group_home.html', context)
@farm_required
def paddock_group_new(request):
if request.method == 'GET':
@@ -99,9 +85,9 @@ def paddock_group_new(request):
elif request.method == 'POST':
success = services.create_paddock_group(request)
if success:
return redirect('entities:farm:paddock_group_home')
return redirect('entities:farm:paddock_group_list')
return redirect('entities:farm:paddock_group_new')
return redirect('entities:farm:paddock_group_create')
@@ -119,4 +105,4 @@ def paddock_group_update(request, paddock_group_id):
@require_http_methods(["POST"])
def paddock_group_delete(request, paddock_group_id):
services.delete_paddock_group(request, paddock_group_id)
return redirect('entities:farm:paddock_group_home')
return redirect('entities:farm:paddock_group_list')
@@ -0,0 +1,23 @@
from .base import BaseListView
from entities.models.animal import Animal
class AnimalListView(BaseListView):
model = Animal
title = "Animais"
columns = [
{"field": "identifier", "label": "Identificador"},
{"field": "age", "label": "Idade"},
{"field": "weight", "label": "Peso"},
]
create_url = "entities:animal:animal_create"
edit_url = "entities:animal:animal_update"
delete_url = "entities:animal:animal_delete"
list_url = "entities:animal:animal_list"
filter_fields = ["identifier"]
def get_queryset(self):
return super().get_queryset().filter(is_active=True, farm=self.selected_farm)
@@ -0,0 +1,57 @@
from django.views.generic import ListView
from django.db.models import Q
from entities.models.farm import Farm
class BaseListView(ListView):
template_name = "entity_list.html"
paginate_by = 10
selected_farm = ""
order_by = ["id"]
# Specific configurations for each entity
title = ""
columns = []
create_url = ""
edit_url = ""
delete_url = ""
list_url = ""
filter_fields = [] # ex: ["identifier", "name"]
# Acts like a init function - provides the select_farm Farm object to all dependent views
def dispatch(self, request, *args, **kwargs):
farm_id = self.request.session.get('selected_farm_id')
self.selected_farm = Farm.objects.filter(id=farm_id).first()
return super().dispatch(request, *args, **kwargs)
# Filter
def get_queryset(self):
queryset = super().get_queryset()
query = self.request.GET.get("q")
if query and self.filter_fields:
q_objects = Q()
for field in self.filter_fields:
q_objects |= Q(**{f"{field}__icontains": query})
queryset = queryset.filter(q_objects)
return queryset.order_by(*self.order_by)
# Context
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update({
"title": self.title,
"columns": self.columns,
"create_url": self.create_url,
"edit_url": self.edit_url,
"delete_url": self.delete_url,
"list_url": self.list_url,
"query": self.request.GET.get("q", ""),
})
return context
@@ -0,0 +1,35 @@
from .base import BaseListView
from django.db.models import Prefetch
from entities.models.animal import Batch, Animal
class BatchListView(BaseListView):
model = Batch
title = "Lotes"
columns = [
{"field": "identifier", "label": "Identificador"},
{"field": "animal_cathgory", "label": "Categoria"},
{"field": "batch_type", "label": "Tipo"},
]
create_url = "entities:animal:batch_create"
edit_url = "entities:animal:batch_update"
delete_url = "entities:animal:batch_delete"
list_url = "entities:animal:batch_list"
filter_fields = ["identifier"]
def get_queryset(self):
queryset = Batch.objects.filter(
batchanimal__animal__farm=self.selected_farm,
is_active=True
).distinct().prefetch_related(
Prefetch('animals', queryset=Animal.objects.filter(is_active=True))
)
query = self.request.GET.get("q")
if query:
queryset = queryset.filter(identifier__icontains=query)
return queryset
@@ -0,0 +1,24 @@
from .base import BaseListView
from entities.models.farm import Paddock
class PaddockListView(BaseListView):
model = Paddock
title = "Piquetes"
columns = [
{"field": "paddock_number", "label": "Número"},
{"field": "area", "label": "Área"},
{"field": "load_capacity", "label": "Capacidade"},
{"field": "occupied", "label": "Ocupado"},
]
create_url = "entities:farm:paddock_group_create"
edit_url = "entities:farm:paddock_group_update"
delete_url = "entities:farm:paddock_group_delete"
list_url = "entities:farm:paddock_group_list"
filter_fields = ["paddock_number"]
def get_queryset(self):
return super().get_queryset().filter(is_active=True, farm=self.selected_farm)
@@ -0,0 +1,35 @@
from .base import BaseListView
from django.db.models import Prefetch
from entities.models.farm import PaddockGroup, Paddock
class PaddockGroupListView(BaseListView):
model = PaddockGroup
title = "Blocos"
columns = [
{"field": "identifier", "label": "Identificador"},
{"field": "description", "label": "Descrição"},
]
create_url = "entities:farm:paddock_group_create"
edit_url = "entities:farm:paddock_group_update"
delete_url = "entities:farm:paddock_group_delete"
list_url = "entities:farm:paddock_group_list"
filter_fields = ["identifier"]
def get_queryset(self):
queryset = PaddockGroup.objects.filter(
paddocks__farm=self.selected_farm,
is_active=True
).distinct().prefetch_related(
Prefetch('paddocks', queryset=Paddock.objects.filter(is_active=True))
)
query = self.request.GET.get("q")
if query:
queryset = queryset.filter(identifier__icontains=query)
return queryset
@@ -0,0 +1,28 @@
from .base import BaseListView
from entities.models.rotation import Rotation
class RotationListView(BaseListView):
model = Rotation
template_name = "rotation/rotation_home.html"
title = "Movimentações"
columns = [
#{"field": "id", "label": "ID"},
{"field": "batch", "label": "Lote"},
{"field": "paddock", "label": "Piquete"},
{"field": "entry_date", "label": "Entrada"},
{"field": "exit_date", "label": "Saída"},
{"field": "animal_count", "label": "Animais"},
{"field": "livestock_unit", "label": "UGM"},
]
create_url = "entities:rotation:rotation_create"
delete_url = "entities:rotation:rotation_delete"
list_url = "entities:rotation:rotation_home"
filter_fields = ["id", "batch", "paddock"]
def get_queryset(self):
return super().get_queryset().filter(is_active=True, paddock__farm=self.selected_farm)
+5 -1
View File
@@ -1,10 +1,14 @@
from django.shortcuts import render
from core.decorators import farm_required
@farm_required
def rotation_home(request):
return render(request, 'rotation/rotation_home.html')
def rotation_new(request):
@farm_required
def rotation_create(request):
return render(request, 'rotation/rotation_new.html')
@farm_required
def rotation_delete(request):
return render(request, 'rotation/rotation_delete.html')
+1 -1
View File
@@ -48,7 +48,7 @@
/* HOVER EFFECTS */
.highlight-hover:hover {
background-color: rgba(255, 255, 255, 0.15);
background-color: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
transition: all var(--transition-speed) ease;
}