Envio do estado atual do projeto
Projeto ainda em desenvolvimento. Enviado para o repositório para manter estado atual.
This commit is contained in:
1 parent
e539e83c79
commit
24650484f1
94 files changed
+3575
No files matched your search
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CoreConfig(AppConfig):
|
||||||
|
name = 'core'
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from entities.models import Farm
|
||||||
|
|
||||||
|
# Injects the selected farm and the list of farms into the template context for authenticated users.
|
||||||
|
def selected_farm_processor(request):
|
||||||
|
selected_farm = None
|
||||||
|
farms = None
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
farms = request.user.farms.only("id", "name")
|
||||||
|
|
||||||
|
farm_id = request.session.get('selected_farm_id')
|
||||||
|
|
||||||
|
if farm_id:
|
||||||
|
try:
|
||||||
|
selected_farm = farms.get(id=farm_id)
|
||||||
|
except Farm.DoesNotExist:
|
||||||
|
request.session.pop('selected_farm_id', None)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'SELECTED_FARM': selected_farm,
|
||||||
|
'farms': farms
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
|
FARM_SELECT_URL = 'entities:user:farm_select'
|
||||||
|
|
||||||
|
def farm_required(view_func):
|
||||||
|
"""
|
||||||
|
Decorator that ensures the user has a farm selected in the session.
|
||||||
|
If there isn't one, it redirects to the farm selection screen.
|
||||||
|
"""
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(request, *args, **kwargs):
|
||||||
|
if 'selected_farm_id' not in request.session:
|
||||||
|
return redirect(reverse(FARM_SELECT_URL))
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Forces the user to be logged in the system
|
||||||
|
# A URL whitelist can be found in the settings.py file
|
||||||
|
class LoginRequiredMiddleware:
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
self.exempt_paths = getattr(settings, 'LOGIN_EXEMPT_PATHS', [])
|
||||||
|
self.exempt_prefixes = getattr(settings, 'LOGIN_EXEMPT_PREFIX', [])
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
path = request.path_info
|
||||||
|
|
||||||
|
if any(path.startswith(prefix) for prefix in self.exempt_prefixes):
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
if path in self.exempt_paths:
|
||||||
|
return self.get_response(request)
|
||||||
|
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect(settings.LOGIN_URL)
|
||||||
|
|
||||||
|
return self.get_response(request)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
Whitespace-only changes.
@@ -0,0 +1,29 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>{{ title }}</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="{% static 'img/favicon/favicon.ico' %}" type="image/x-icon">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/reset.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/variables.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/global.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/buttons.css' %}">
|
||||||
|
|
||||||
|
<!-- Importing Font Awesome -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
|
||||||
|
{% block extra_css %}{% endblock %}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{% include 'includes/_sidebar.html' %}
|
||||||
|
{% include 'includes/_navbar.html' %}
|
||||||
|
|
||||||
|
<div class="main-content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script src="{% static 'js/main.js' %}"></script>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Seleção de Fazenda</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="{% static 'img/favicon/favicon.ico' %}" type="image/x-icon">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/reset.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/variables.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/global.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/buttons.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'user/css/farm_select.css' %}">
|
||||||
|
|
||||||
|
<!-- Importing Font Awesome -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="farm-select-navbar">
|
||||||
|
<div class="navbar-brand">
|
||||||
|
MultiFarm
|
||||||
|
</div>
|
||||||
|
<div class="navbar-user-info">
|
||||||
|
<span class="username">Olá, {{ request.user.short_name }}</span>
|
||||||
|
<a href="{% url 'entities:user:user_logout' %}"><i class="fa-solid fa-right-from-bracket"></i></a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="farm-select-container">
|
||||||
|
<h1>Selecione sua fazenda</h1>
|
||||||
|
|
||||||
|
{% if farms %}
|
||||||
|
<ul class="farm-list">
|
||||||
|
{% for farm in farms %}
|
||||||
|
<li>
|
||||||
|
<a href="{% url 'entities:user:farm_set' farm_id=farm.id %}" class="farm-button">
|
||||||
|
<div class="farm-item">
|
||||||
|
<h3 class="farm-name">{{ farm.name }}</h3>
|
||||||
|
<p class="farm-description">
|
||||||
|
<span class="total-area">Área total: {{ farm.total_area }}m²</span>
|
||||||
|
<span class="count-piquetes">Piquetes: {{ farm.total_paddocks }}</span>
|
||||||
|
<span class="count-animais">Animais: {{ farm.total_animals }}</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<div class="no-farms">
|
||||||
|
<p>Nenhuma fazenda encontrada</p>
|
||||||
|
<p><a href="#"><i class="fa-solid fa-plus"></i> Criar nova fazenda</a></p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<form method="POST" action="{{ action_url }}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
<button class="btn-primary" type="submit">Salvar</button>
|
||||||
|
<a href="{{ return_url }}" class="btn-simple">Cancelar</a>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<nav class="navbar">
|
||||||
|
<div class="w33">
|
||||||
|
<i class="fa-solid fa-bars" id="sidebar-toggle"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% comment %} <div class="navbar-farm-selector w33">
|
||||||
|
<span class="farm-name"><i class="fa-regular fa-circle-check"></i> {{ SELECTED_FARM.name }}</span>
|
||||||
|
<a href="{% url 'entities:user:farm_deselect' %}" class="btn-simple">Alterar</a>
|
||||||
|
</div> {% endcomment %}
|
||||||
|
|
||||||
|
<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 }}">
|
||||||
|
<select name="farm_id" onchange="this.form.submit()">
|
||||||
|
{% for farm in farms %}
|
||||||
|
<option value="{{ farm.id }}" {% if farm.id == SELECTED_FARM.id %}selected{% endif %}>
|
||||||
|
{{ farm.name }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="navbar-user-info w33">
|
||||||
|
<span class="username">Olá, {{ request.user.short_name }}</span>
|
||||||
|
<a href="{% url 'entities:user:farm_deselect' %}"><i class="fa-solid fa-user-pen"></i></a>
|
||||||
|
<a href="{% url 'entities:user:user_logout' %}"><i class="fa-solid fa-right-from-bracket"></i></a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
<aside class="sidebar">
|
||||||
|
<div class="sidebar-brand">
|
||||||
|
<a href="{% url 'entities:farm:farm_home' %}">MultiFarm</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="sidebar-menu">
|
||||||
|
<li class="sidebar-header">ACOMPANHAMENTO</li>
|
||||||
|
|
||||||
|
<li class="{% if request.resolver_match.url_name != 'report' %}highlight-hover{% endif %}">
|
||||||
|
<a href="{% url 'entities:farm:farm_home' %}" class="sidebar-link {% if request.resolver_match.url_name == 'report' %}active{% endif %}"><i class="fa-solid fa-chart-pie"></i> Relatórios</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
{% if user.is_staff or user.is_superuser %}
|
||||||
|
<li class="{% if request.resolver_match.url_name not in 'admin' %}highlight-hover{% endif %}">
|
||||||
|
<a href="{% url 'admin:index' %}" class="sidebar-link"><i class="fa-solid fa-user-tie"></i> Painel Administrativo</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</aside>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="pt-br">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Login</title>
|
||||||
|
|
||||||
|
<link rel="shortcut icon" href="{% static 'img/favicon/favicon.ico' %}" type="image/x-icon">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/reset.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/base/variables.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/global.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/layout/buttons.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'user/css/login.css' %}">
|
||||||
|
|
||||||
|
<!-- Importing Font Awesome -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/7.0.1/css/all.min.css" integrity="sha512-2SwdPD6INVrV/lHTZbO2nodKhrnDdJK9/kg2XD1r9uGqPo1cUbujc+IYdlYdEErWNu69gVcYgdxlmVmzTWnetw==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="ilustration-container">
|
||||||
|
<h1>Multifarm pastoreio</h1>
|
||||||
|
</div>
|
||||||
|
<div class="login-form-container">
|
||||||
|
<h1>Login</h1>
|
||||||
|
<form method="POST" action="{% url 'entities:user:user_login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form }}
|
||||||
|
{% if fail_login %}
|
||||||
|
<p class="login-fail"><i class="fa-solid fa-circle-exclamation"></i> Login ou senha incorretos</p>
|
||||||
|
{% endif %}
|
||||||
|
<button type="submit">Entrar <i class="fa-solid fa-right-to-bracket"></i></button>
|
||||||
|
<p>Não possui cadastro? <a href="{% url 'entities:user:user_signin' %}">Solicite acesso</a>.</p>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
Whitespace-only changes.
@@ -0,0 +1,18 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from .models.user import User
|
||||||
|
from .models.farm import Farm, PaddockGroup, Paddock
|
||||||
|
from .models.animal import Animal, Batch, BatchAnimal, FeedingRecord
|
||||||
|
from .models.rotation import Rotation
|
||||||
|
|
||||||
|
admin.site.register([
|
||||||
|
User,
|
||||||
|
Farm,
|
||||||
|
PaddockGroup,
|
||||||
|
Paddock,
|
||||||
|
Animal,
|
||||||
|
Batch,
|
||||||
|
BatchAnimal,
|
||||||
|
FeedingRecord,
|
||||||
|
Rotation,
|
||||||
|
])
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class EntitiesConfig(AppConfig):
|
||||||
|
name = 'entities'
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
from .animal import *
|
||||||
|
from .farm import *
|
||||||
|
from .rotation import *
|
||||||
|
from .user import *
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
from django import forms
|
||||||
|
from ..models.animal import Batch, Animal
|
||||||
|
|
||||||
|
class AnimalCreateForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Animal
|
||||||
|
fields = ['identifier', 'birth', 'weight']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'identifier': 'Identificador',
|
||||||
|
'birth': 'Data de Nascimento',
|
||||||
|
'weight': 'Peso (kg)',
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'birth': forms.DateInput(attrs={'type': 'date'}, format='%Y-%m-%d'),
|
||||||
|
'weight': forms.NumberInput(attrs={'step': '0.01'})
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimalEditForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Animal
|
||||||
|
fields = ['identifier', 'weight']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'identifier': 'Identificador',
|
||||||
|
'weight': 'Peso (kg)',
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'weight': forms.NumberInput(attrs={'step': '0.01'})
|
||||||
|
}
|
||||||
|
|
||||||
|
class BatchForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Batch
|
||||||
|
fields = ['identifier', 'animal_category', 'batch_type', 'animals']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'identifier': 'Identificador',
|
||||||
|
'animal_category': 'Categoria animal',
|
||||||
|
'batch_type': 'Tipo',
|
||||||
|
'animals': 'Animais',
|
||||||
|
}
|
||||||
|
BatchCreateForm = BatchEditForm = BatchForm # Alias to BatchForm since its implementations are the same
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
from django import forms
|
||||||
|
from ..models import Farm, PaddockGroup, Paddock
|
||||||
|
|
||||||
|
class FarmForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Farm
|
||||||
|
fields = ['name', 'total_area']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'name': 'Nome',
|
||||||
|
'total_area': 'Área total',
|
||||||
|
}
|
||||||
|
FarmCreateForm = FarmEditForm = FarmForm # Alias since its implementations are the same
|
||||||
|
|
||||||
|
class PaddockGroupForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = PaddockGroup
|
||||||
|
fields = ['identifier', 'description']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'identifier': 'Identificador',
|
||||||
|
'description': 'Descrição',
|
||||||
|
}
|
||||||
|
PaddockGroupCreateForm = PaddockGroupEditForm = PaddockGroupForm
|
||||||
|
|
||||||
|
class PaddockForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Paddock
|
||||||
|
fields = ['paddock_number', 'area', 'load_capacity', 'paddock_group']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'paddock_number': 'Número do piquete',
|
||||||
|
'area': 'Área',
|
||||||
|
'load_capacity': 'Lotação máxima',
|
||||||
|
'paddock_group': 'Bloco',
|
||||||
|
}
|
||||||
|
PaddockCreateForm = PaddockEditForm = PaddockForm
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from django import forms
|
||||||
|
from ..models import Rotation
|
||||||
|
|
||||||
|
class RotationEntryForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Rotation
|
||||||
|
fields = ['batch', 'entry_height', 'paddock']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'batch': 'Lote de animais',
|
||||||
|
'entry_height': 'Altura do pasto',
|
||||||
|
'paddock': 'Piquete',
|
||||||
|
}
|
||||||
|
|
||||||
|
class RotationExitForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = Rotation
|
||||||
|
fields = ['batch', 'exit_height']
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'batch': 'Lote de animais',
|
||||||
|
'exit_height': 'Altura do pasto',
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from django import forms
|
||||||
|
from ..models import User
|
||||||
|
|
||||||
|
class UserCreateForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['name', 'login', 'email',]
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'name': 'Nome',
|
||||||
|
'login': 'Login',
|
||||||
|
'email': 'E-mail',
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'email': forms.EmailInput(),
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserEditForm(forms.ModelForm):
|
||||||
|
class Meta:
|
||||||
|
model = User
|
||||||
|
fields = ['name', 'email',]
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
'name': 'Nome',
|
||||||
|
'email': 'E-mail',
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'email': forms.EmailInput(),
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserLoginForm(forms.Form):
|
||||||
|
login = forms.CharField(max_length=45, label='Usuário')
|
||||||
|
password = forms.CharField(widget=forms.PasswordInput, label='Senha')
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
# Generated by Django 6.0.2 on 2026-02-28 19:45
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import entities.models.user
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Animal',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('identifier', models.CharField(max_length=45)),
|
||||||
|
('birth', models.DateField()),
|
||||||
|
('weight', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Batch',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('identifier', models.CharField(max_length=45)),
|
||||||
|
('animal_category', models.CharField(blank=True, max_length=45, null=True)),
|
||||||
|
('batch_type', models.CharField(blank=True, max_length=45, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Farm',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('name', models.CharField(max_length=45)),
|
||||||
|
('total_area', models.IntegerField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='PaddockGroup',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('identifier', models.CharField(max_length=45)),
|
||||||
|
('description', models.TextField(blank=True, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BatchAnimal',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('animal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='entities.animal')),
|
||||||
|
('batch', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='entities.batch')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='batch',
|
||||||
|
name='animals',
|
||||||
|
field=models.ManyToManyField(related_name='batches', through='entities.BatchAnimal', to='entities.animal'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='animal',
|
||||||
|
name='farm',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='animals', to='entities.farm'),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='User',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('name', models.CharField(max_length=45)),
|
||||||
|
('login', models.CharField(max_length=45, unique=True)),
|
||||||
|
('email', models.EmailField(max_length=254, unique=True)),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('is_superuser', models.BooleanField(default=False)),
|
||||||
|
('is_staff', models.BooleanField(default=False)),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now)),
|
||||||
|
('last_login', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||||
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||||
|
('farms', models.ManyToManyField(related_name='users', to='entities.farm')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'User',
|
||||||
|
'verbose_name_plural': 'Users',
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', entities.models.user.CustomUserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Paddock',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('paddock_number', models.IntegerField()),
|
||||||
|
('area', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||||
|
('load_capacity', models.IntegerField(blank=True, null=True)),
|
||||||
|
('occupied', models.BooleanField(default=False)),
|
||||||
|
('farm', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='paddocks', to='entities.farm')),
|
||||||
|
('paddock_group', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paddocks', to='entities.paddockgroup')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rotation',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('is_active', models.BooleanField(default=True)),
|
||||||
|
('entry_date', models.DateTimeField()),
|
||||||
|
('entry_height', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('exit_date', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('exit_height', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)),
|
||||||
|
('animal_count', models.IntegerField()),
|
||||||
|
('livestock_unit', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('batch', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='rotations', to='entities.batch')),
|
||||||
|
('paddock', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rotations', to='entities.paddock')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='FeedingRecord',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('feed_quantity', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||||
|
('animal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeding_record', to='entities.animal')),
|
||||||
|
('rotation', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feeding_record', to='entities.rotation')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='batchanimal',
|
||||||
|
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('animal',), name='unique_active_animal_batch_link'),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='animal',
|
||||||
|
constraint=models.UniqueConstraint(condition=models.Q(('is_active', True)), fields=('identifier', 'farm'), name='unique_active_farm_animal'),
|
||||||
|
),
|
||||||
|
]
|
||||||
Whitespace-only changes.
@@ -0,0 +1,4 @@
|
|||||||
|
from .user import *
|
||||||
|
from .farm import *
|
||||||
|
from .animal import *
|
||||||
|
from .rotation import *
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
from datetime import date
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import Q
|
||||||
|
|
||||||
|
class Animal(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
identifier = models.CharField(max_length=45)
|
||||||
|
birth = models.DateField()
|
||||||
|
weight = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
farm = models.ForeignKey('entities.Farm', on_delete=models.CASCADE, related_name='animals')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Animal {self.identifier} - birth {self.birth} - weight {self.weight} - farm {self.farm} - is_active {self.is_active}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def age(self):
|
||||||
|
today = date.today()
|
||||||
|
birth_date = self.birth
|
||||||
|
|
||||||
|
years = today.year - birth_date.year
|
||||||
|
months = today.month - birth_date.month
|
||||||
|
|
||||||
|
if today.day < birth_date.day:
|
||||||
|
months -= 1
|
||||||
|
|
||||||
|
if months < 0:
|
||||||
|
months += 12
|
||||||
|
years -= 1
|
||||||
|
|
||||||
|
if years > 0 and months > 0:
|
||||||
|
return f"{years} anos e {months} meses" # YYYY years and MM months
|
||||||
|
elif years > 0:
|
||||||
|
return f"{years} anos" # YYYY years
|
||||||
|
elif months > 0:
|
||||||
|
return f"{months} meses" # MM months
|
||||||
|
else:
|
||||||
|
return "Menos de um mês" # Less than a month
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['identifier', 'farm'],
|
||||||
|
condition=Q(is_active=True),
|
||||||
|
name='unique_active_farm_animal'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
class Batch(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
identifier = models.CharField(max_length=45)
|
||||||
|
animal_category = models.CharField(max_length=45, null=True, blank=True)
|
||||||
|
batch_type = models.CharField(max_length=45, null=True, blank=True)
|
||||||
|
animals = models.ManyToManyField(Animal, through='BatchAnimal', related_name='batches')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.__original_is_active = self.is_active
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
is_active_changed = self.__original_is_active != self.is_active
|
||||||
|
|
||||||
|
if is_active_changed:
|
||||||
|
if self.is_active is False:
|
||||||
|
self.batchanimal_set.all().update(is_active=False)
|
||||||
|
|
||||||
|
elif self.is_active is True:
|
||||||
|
self.batchanimal_set.all().update(is_active=True)
|
||||||
|
|
||||||
|
self.__original_is_active = self.is_active
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.identifier
|
||||||
|
|
||||||
|
class BatchAnimal(models.Model):
|
||||||
|
animal = models.ForeignKey(Animal, on_delete=models.CASCADE)
|
||||||
|
batch = models.ForeignKey(Batch, on_delete=models.CASCADE)
|
||||||
|
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
|
||||||
|
# When reactivating a batch, all its animals must not belong to any other active batch.
|
||||||
|
# Otherwise, the database will raise an IntegrityError due to the unique constraint on active animals.
|
||||||
|
# This is a rare use case, but any future code regarding it must implement error treatment
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=['animal'],
|
||||||
|
condition=Q(is_active=True),
|
||||||
|
name='unique_active_animal_batch_link'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.animal.identifier} in {self.batch.identifier}"
|
||||||
|
|
||||||
|
class FeedingRecord(models.Model):
|
||||||
|
animal = models.ForeignKey(Animal, on_delete=models.CASCADE, related_name='feeding_record')
|
||||||
|
rotation = models.ForeignKey('entities.Rotation', on_delete=models.CASCADE, related_name='feeding_record')
|
||||||
|
feed_quantity = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Consumo de {self.animal.identifier} - {self.feed_quantity} kg"
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Farm(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
name = models.CharField(max_length=45)
|
||||||
|
total_area = models.IntegerField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class PaddockGroup(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
identifier = models.CharField(max_length=45)
|
||||||
|
description = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Bloco {self.id}"
|
||||||
|
|
||||||
|
|
||||||
|
class Paddock(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
paddock_number = models.IntegerField()
|
||||||
|
area = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
||||||
|
load_capacity = models.IntegerField(null=True, blank=True)
|
||||||
|
occupied = models.BooleanField(default=False)
|
||||||
|
farm = models.ForeignKey(Farm, on_delete=models.CASCADE, related_name='paddocks')
|
||||||
|
paddock_group = models.ForeignKey(PaddockGroup, on_delete=models.SET_NULL, related_name='paddocks', null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Piquete {self.paddock_number} - Bloco {self.paddock_group.id}"
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
class Rotation(models.Model):
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
entry_date = models.DateTimeField()
|
||||||
|
entry_height = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
exit_date = models.DateTimeField(null=True, blank=True)
|
||||||
|
exit_height = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
|
||||||
|
animal_count = models.IntegerField()
|
||||||
|
livestock_unit = models.DecimalField(max_digits=10, decimal_places=2)
|
||||||
|
batch = models.ForeignKey('entities.Batch', on_delete=models.SET_NULL, related_name='rotations', null=True, blank=True)
|
||||||
|
paddock = models.ForeignKey('entities.Paddock', on_delete=models.CASCADE, related_name='rotations')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"Movimentação {self.id} - Lote {self.batch.identifier}"
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from django.db import models
|
||||||
|
from django.contrib.auth.models import UserManager, AbstractBaseUser, PermissionsMixin
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
class CustomUserManager(UserManager):
|
||||||
|
def _create_user(self, name, login, email, password, **extra_fields):
|
||||||
|
if not login:
|
||||||
|
raise ValueError("Information provided is not a valid login")
|
||||||
|
if not email:
|
||||||
|
raise ValueError("You must inform an e-mail adress")
|
||||||
|
|
||||||
|
email = self.normalize_email(email)
|
||||||
|
user = self.model(
|
||||||
|
name = name,
|
||||||
|
login = login,
|
||||||
|
email = email,
|
||||||
|
**extra_fields)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save(using=self.db)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
def create_user(self, name=None, login=None, email=None, password=None, **extra_fields):
|
||||||
|
extra_fields.setdefault('is_staff', False)
|
||||||
|
extra_fields.setdefault('is_superuser', False)
|
||||||
|
return self._create_user(name, login, email, password, **extra_fields)
|
||||||
|
|
||||||
|
def create_superuser(self, name=None, login=None, email=None, password=None, **extra_fields):
|
||||||
|
extra_fields.setdefault('is_staff', True)
|
||||||
|
extra_fields.setdefault('is_superuser', True)
|
||||||
|
return self._create_user(name, login, email, password, **extra_fields)
|
||||||
|
|
||||||
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
|
name = models.CharField(max_length=45)
|
||||||
|
login = models.CharField(max_length=45, unique=True)
|
||||||
|
email = models.EmailField(unique=True)
|
||||||
|
|
||||||
|
farms = models.ManyToManyField('entities.Farm', related_name='users')
|
||||||
|
|
||||||
|
is_active = models.BooleanField(default=True)
|
||||||
|
is_superuser = models.BooleanField(default=False)
|
||||||
|
is_staff = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
date_joined = models.DateTimeField(default=timezone.now)
|
||||||
|
last_login = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
objects = CustomUserManager()
|
||||||
|
|
||||||
|
USERNAME_FIELD = 'login'
|
||||||
|
EMAIL_FIELD = 'email'
|
||||||
|
REQUIRED_FIELDS = ['name', 'email']
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = 'User'
|
||||||
|
verbose_name_plural = 'Users'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def full_name(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def short_name(self):
|
||||||
|
return self.name.split(' ')[0] or self.email.split('@')[0]
|
||||||
Whitespace-only changes.
@@ -0,0 +1,227 @@
|
|||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
|
||||||
|
from ..models import Animal, Batch
|
||||||
|
from ..forms import AnimalCreateForm, AnimalEditForm, BatchForm
|
||||||
|
from entities.models import Farm
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_farm(request):
|
||||||
|
return Farm.objects.get(id=request.session['selected_farm_id'])
|
||||||
|
|
||||||
|
|
||||||
|
# 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')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def create_animal(request):
|
||||||
|
form = AnimalCreateForm(request.POST)
|
||||||
|
selected_farm = get_selected_farm(request)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
animal = form.save(commit=False)
|
||||||
|
animal.farm = selected_farm
|
||||||
|
animal.save()
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_animal(request, animal_id):
|
||||||
|
farm_id = request.session['selected_farm_id']
|
||||||
|
animal = get_object_or_404(Animal, pk=animal_id, farm_id=farm_id)
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['title'] = 'Editar animal'
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:animal:animal_update',
|
||||||
|
kwargs={'animal_id': animal.pk}
|
||||||
|
)
|
||||||
|
context['return_url'] = reverse('entities:animal:animal_home')
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = AnimalEditForm(instance=animal)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = AnimalEditForm(request.POST, instance=animal)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return {'redirect': 'entities:animal:animal_home'}
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_animal(request, animal_id):
|
||||||
|
farm_id = request.session['selected_farm_id']
|
||||||
|
animal = get_object_or_404(Animal, pk=animal_id, farm_id=farm_id)
|
||||||
|
animal.is_active = False
|
||||||
|
animal.save()
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = BatchForm()
|
||||||
|
form.fields['animals'].queryset = Animal.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': 'Novo Lote',
|
||||||
|
'form': form,
|
||||||
|
'action_url': reverse('entities:animal:batch_new'),
|
||||||
|
'return_url': reverse('entities:animal:batch_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = BatchForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
batch = form.save(commit=False)
|
||||||
|
batch.save()
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
|
return {'redirect': 'entities:animal:batch_home'}
|
||||||
|
|
||||||
|
form.fields['animals'].queryset = Animal.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': 'Novo Lote',
|
||||||
|
'form': form,
|
||||||
|
'action_url': reverse('entities:animal:batch_new'),
|
||||||
|
'return_url': reverse('entities:animal:batch_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def update_batch(request, batch_id):
|
||||||
|
batch_old = get_object_or_404(Batch, pk=batch_id, is_active=True)
|
||||||
|
selected_farm = get_selected_farm(request)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': f'Editar Lote {batch_old.identifier}',
|
||||||
|
'return_url': reverse('entities:animal:batch_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = BatchForm(instance=batch_old)
|
||||||
|
|
||||||
|
form.fields['animals'].queryset = Animal.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:animal:batch_update',
|
||||||
|
kwargs={'batch_id': batch_old.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = BatchForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
batch_new = form.save(commit=False)
|
||||||
|
batch_new.is_active = True
|
||||||
|
|
||||||
|
batch_old.is_active = False
|
||||||
|
batch_old.save()
|
||||||
|
|
||||||
|
batch_new.save()
|
||||||
|
form.save_m2m()
|
||||||
|
|
||||||
|
return {'redirect': 'entities:animal:batch_home'}
|
||||||
|
|
||||||
|
form.fields['animals'].queryset = Animal.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:animal:batch_update',
|
||||||
|
kwargs={'batch_id': batch_old.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_batch(batch_id):
|
||||||
|
batch = get_object_or_404(Batch, pk=batch_id, is_active=True)
|
||||||
|
batch.is_active = False
|
||||||
|
batch.save()
|
||||||
@@ -0,0 +1,293 @@
|
|||||||
|
from django.shortcuts import get_object_or_404
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
|
||||||
|
from ..models import Farm, Paddock, PaddockGroup
|
||||||
|
from ..forms import FarmCreateForm, FarmEditForm, PaddockCreateForm, PaddockEditForm, PaddockGroupCreateForm, PaddockGroupEditForm
|
||||||
|
|
||||||
|
|
||||||
|
def get_selected_farm(request):
|
||||||
|
return Farm.objects.get(id=request.session['selected_farm_id'])
|
||||||
|
|
||||||
|
|
||||||
|
# Farm Services
|
||||||
|
def get_farm_home_context(request):
|
||||||
|
authenticated_user = request.user
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['title'] = 'Fazenda'
|
||||||
|
|
||||||
|
farms = Farm.objects.filter(
|
||||||
|
users=authenticated_user, # Verificar se da para deixar apenas = ou deve usar um is in ou algo do tipo
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['farms'] = farms
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def get_farm_new_context():
|
||||||
|
form = FarmCreateForm()
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['title'] = 'Nova fazenda'
|
||||||
|
context['form'] = form
|
||||||
|
context['action_url'] = reverse('entities:farm:farm_new')
|
||||||
|
context['return_url'] = reverse('entities:farm:farm_home')
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def create_farm(request):
|
||||||
|
form = FarmCreateForm(request.POST)
|
||||||
|
authenticated_user = request.user
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
farm = form.save()
|
||||||
|
farm.users.add(authenticated_user)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def update_farm(request, farm_id):
|
||||||
|
farm = get_object_or_404(Farm, pk=farm_id, is_active=True)
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['title'] = 'Editar fazenda'
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:farm:farm_update',
|
||||||
|
kwargs={'farm_id': farm.pk}
|
||||||
|
)
|
||||||
|
context['return_url'] = reverse('entities:farm:farm_home')
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = FarmEditForm(instance=farm)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = FarmEditForm(request.POST, instance=farm)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return {'redirect': 'entities:farm:farm_home'}
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_farm(farm_id):
|
||||||
|
farm = get_object_or_404(Farm, pk=farm_id, is_active=True)
|
||||||
|
farm.is_active = False
|
||||||
|
farm.save()
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = PaddockCreateForm()
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': 'Novo Piquete',
|
||||||
|
'form': form,
|
||||||
|
'action_url': reverse('entities:farm:paddock_new'),
|
||||||
|
'return_url': reverse('entities:farm:paddock_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = PaddockCreateForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
paddock = form.save(commit=False)
|
||||||
|
paddock.farm = selected_farm
|
||||||
|
paddock.save()
|
||||||
|
|
||||||
|
return {'redirect': 'entities:farm:paddock_home'}
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': 'Novo Piquete',
|
||||||
|
'form': form,
|
||||||
|
'action_url': reverse('entities:farm:paddock_new'),
|
||||||
|
'return_url': reverse('entities:farm:paddock_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def update_paddock(request, paddock_id):
|
||||||
|
farm_id = request.session['selected_farm_id']
|
||||||
|
paddock = get_object_or_404(Paddock, pk=paddock_id, farm_id=farm_id, is_active=True)
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['title'] = 'Editar piquete'
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:farm:paddock_update',
|
||||||
|
kwargs={'paddock_id': paddock.pk}
|
||||||
|
)
|
||||||
|
context['return_url'] = reverse('entities:farm:paddock_home')
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = PaddockEditForm(instance=paddock)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = PaddockEditForm(request.POST, instance=paddock)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
form.save()
|
||||||
|
return {'redirect': 'entities:farm:paddock_home'}
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_paddock(paddock_id):
|
||||||
|
paddock = get_object_or_404(Paddock, pk=paddock_id, is_active=True)
|
||||||
|
paddock.is_active = False
|
||||||
|
paddock.save()
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = PaddockGroupCreateForm()
|
||||||
|
form.fields['paddocks'].queryset = Paddock.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if request.method == 'POST':
|
||||||
|
form = PaddockGroupCreateForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
paddock_group = form.save(commit=False)
|
||||||
|
paddock_group.save()
|
||||||
|
|
||||||
|
return {'redirect': 'entities:farm:paddock_group_home'}
|
||||||
|
|
||||||
|
form.fields['paddocks'].queryset = Paddock.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': 'Novo Bloco',
|
||||||
|
'form': form,
|
||||||
|
'action_url': reverse('entities:farm:paddock_group_new'),
|
||||||
|
'return_url': reverse('entities:farm:paddock_group_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def update_paddock_group(request, paddock_group_id):
|
||||||
|
selected_farm = get_selected_farm(request)
|
||||||
|
paddock_group = get_object_or_404(PaddockGroup, pk=paddock_group_id, is_active=True)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'title': f'Editar Lote {paddock_group.identifier}',
|
||||||
|
'return_url': reverse('entities:farm:paddock_group_home')
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.method == 'GET':
|
||||||
|
form = PaddockGroupEditForm(instance=paddock_group)
|
||||||
|
|
||||||
|
form.fields['paddocks'].queryset = Paddock.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:farm:paddock_group_update',
|
||||||
|
kwargs={'paddock_group_id': paddock_group.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
form = PaddockGroupEditForm(request.POST, instance=paddock_group)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
paddock_group = form.save()
|
||||||
|
paddock_group.save()
|
||||||
|
|
||||||
|
return {'redirect': 'entities:farm:paddock_group_home'}
|
||||||
|
|
||||||
|
form.fields['paddocks'].queryset = Paddock.objects.filter(
|
||||||
|
farm=selected_farm,
|
||||||
|
is_active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
context['action_url'] = reverse(
|
||||||
|
'entities:farm:paddock_group_update',
|
||||||
|
kwargs={'paddock_group_id': paddock_group.pk}
|
||||||
|
)
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def delete_paddock_group(paddock_group_id):
|
||||||
|
paddock_group = get_object_or_404(PaddockGroup, pk=paddock_group_id, is_active=True)
|
||||||
|
paddock_group.is_active = False
|
||||||
|
paddock_group.save()
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib.auth import authenticate, login, logout
|
||||||
|
from django.db.models import Count
|
||||||
|
|
||||||
|
from ..forms import UserLoginForm
|
||||||
|
|
||||||
|
|
||||||
|
def user_profile(request): # TO DO
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def user_login(request):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return {'redirect': 'entities:user:farm_select'}
|
||||||
|
|
||||||
|
context = {}
|
||||||
|
context['fail_login'] = False
|
||||||
|
form = UserLoginForm()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = UserLoginForm(request.POST)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
username = form.cleaned_data['login']
|
||||||
|
password = form.cleaned_data['password']
|
||||||
|
user = authenticate(request, username=username, password=password)
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
login(request, user)
|
||||||
|
context['title'] = 'Fazendas'
|
||||||
|
return {'redirect': 'entities:user:farm_select'}
|
||||||
|
else:
|
||||||
|
context['fail_login'] = True
|
||||||
|
else:
|
||||||
|
context['fail_login'] = True
|
||||||
|
|
||||||
|
context['form'] = form
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def user_logout(request):
|
||||||
|
logout(request)
|
||||||
|
return redirect('entities:user:user_login')
|
||||||
|
|
||||||
|
|
||||||
|
def user_signin(request): # TO DO
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
def farm_select(request):
|
||||||
|
if 'selected_farm_id' in request.session:
|
||||||
|
return {'redirect': 'entities:farm:farm_home'}
|
||||||
|
|
||||||
|
farms_query = request.user.farms.filter(is_active=True)
|
||||||
|
farms_with_counts = farms_query.annotate(
|
||||||
|
total_animals=Count('animals'),
|
||||||
|
total_paddocks=Count('paddocks')
|
||||||
|
)
|
||||||
|
|
||||||
|
context = {
|
||||||
|
'farms': farms_with_counts
|
||||||
|
}
|
||||||
|
|
||||||
|
return {'context': context}
|
||||||
|
|
||||||
|
|
||||||
|
def farm_set(request, farm_id=None):
|
||||||
|
# Allows setting the selected farm either via URL parameter or POST data
|
||||||
|
farm_id = farm_id or request.POST.get("farm_id")
|
||||||
|
next_url = request.POST.get("next")
|
||||||
|
|
||||||
|
if not farm_id:
|
||||||
|
return redirect('entities:user:farm_select')
|
||||||
|
|
||||||
|
try:
|
||||||
|
farm = request.user.farms.get(id=farm_id)
|
||||||
|
request.session['selected_farm_id'] = farm.id
|
||||||
|
|
||||||
|
return redirect(next_url or 'entities:farm:farm_home')
|
||||||
|
|
||||||
|
except request.user.farms.model.DoesNotExist:
|
||||||
|
return redirect('entities:user:farm_select')
|
||||||
|
|
||||||
|
def farm_deselect(request):
|
||||||
|
if 'selected_farm_id' in request.session:
|
||||||
|
del request.session['selected_farm_id']
|
||||||
|
|
||||||
|
return redirect('entities:user:farm_select')
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/* ===== PAGE HEADER ===== */
|
||||||
|
.animal_title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animal_title h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
margin: 0;
|
||||||
|
margin-right: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SEARCH FORM ===== */
|
||||||
|
.form_animal_search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_animal_search label {
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Input com altura igual ao botão */
|
||||||
|
.form_animal_search 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_animal_search input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Botão buscar */
|
||||||
|
.form_animal_search 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_animal_search 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 ===== */
|
||||||
|
.animal_title > 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animal_title > 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-sm);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
background: var(--color-bg-dark);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/* ===== PAGE HEADER ===== */
|
||||||
|
.batch_title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch_title h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
margin: 0;
|
||||||
|
margin-right: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SEARCH FORM ===== */
|
||||||
|
.form_batch_search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_batch_search label {
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_batch_search 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_batch_search input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_batch_search 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_batch_search 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 ===== */
|
||||||
|
.batch_title > 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch_title > 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-sm);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
background: var(--color-bg-dark);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 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;
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,94 @@
|
|||||||
|
/* ===== PAGE HEADER ===== */
|
||||||
|
.paddock_title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddock_title h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
margin: 0;
|
||||||
|
margin-right: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SEARCH FORM ===== */
|
||||||
|
.form_paddock_search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_search label {
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_search 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_search input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_search 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_search 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 ===== */
|
||||||
|
.paddock_title > 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddock_title > a:hover {
|
||||||
|
background: var(--color-success);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
/* ===== PAGE HEADER ===== */
|
||||||
|
.paddock_group_title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-md);
|
||||||
|
margin-bottom: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddock_group_title h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
margin: 0;
|
||||||
|
margin-right: var(--space-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== SEARCH FORM ===== */
|
||||||
|
.form_paddock_group_search {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_group_search label {
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_group_search 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_group_search input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_group_search 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form_paddock_group_search button:hover {
|
||||||
|
background: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== NEW BUTTON ===== */
|
||||||
|
.paddock_group_title > 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.paddock_group_title > a:hover {
|
||||||
|
background: var(--color-success);
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,162 @@
|
|||||||
|
/* CONTENT */
|
||||||
|
body {
|
||||||
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
padding-top: var(--header-height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NAVBAR */
|
||||||
|
.farm-select-navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: var(--header-height);
|
||||||
|
background-color: var(--color-bg-light);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--space-lg);
|
||||||
|
z-index: 950;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SITE'S TITLE */
|
||||||
|
.farm-select-navbar .navbar-brand {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--color-bg-sidebar, #444);
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .navbar-user-info and .username uses gap and align style from global.css */
|
||||||
|
|
||||||
|
/* SELECT CONTAINER */
|
||||||
|
.farm-select-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
margin: var(--space-xl) auto;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-select-container h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LIST OF FARMS */
|
||||||
|
.farm-list {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-button {
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-item {
|
||||||
|
padding: var(--space-md);
|
||||||
|
text-align: left;
|
||||||
|
background-color: #ffffff;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-left: 5px solid transparent;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-item:hover {
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
|
||||||
|
border-color: var(--color-farm-select-hover);
|
||||||
|
border-left-color: var(--color-farm-select-hover); /* Cor primária */
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
margin: 0 0 var(--space-xs);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-description {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
align-items: center;
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DATA SEPARATOR */
|
||||||
|
.total-area, .count-piquetes, .count-animais {
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--color-farm-select-info);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-area:after, .count-piquetes:after {
|
||||||
|
content: "|";
|
||||||
|
margin-left: var(--space-xs);
|
||||||
|
color: var(--color-simple-light-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* WHEN NO FARMS */
|
||||||
|
.no-farms {
|
||||||
|
padding: var(--space-lg);
|
||||||
|
margin-top: var(--space-lg);
|
||||||
|
border: 2px dashed #ccc;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
background-color: var(--color-bg-white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-farms p {
|
||||||
|
color: var(--color-farm-select-info);
|
||||||
|
font-size: var(--font-md);
|
||||||
|
margin: var(--space-xs) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NEW FARM BUTTON */
|
||||||
|
.no-farms a {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
padding: var(--space-xs) var(--space-md);
|
||||||
|
background-color: var(--color-new-farm-button);
|
||||||
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-farms a:hover {
|
||||||
|
background-color: var(--color-new-farm-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.farm-select-navbar {
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
}
|
||||||
|
.farm-select-container {
|
||||||
|
padding: var(--space-md) var(--space-sm);
|
||||||
|
margin: var(--header-height) 0 0 0;
|
||||||
|
max-width: 100%;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--color-bg-light);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-container {
|
||||||
|
display: flex;
|
||||||
|
width: 850px;
|
||||||
|
max-width: 90%;
|
||||||
|
min-height: 450px;
|
||||||
|
border-radius: var(--border-radius-lg, 10px);
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ilustration-container {
|
||||||
|
flex: 1;
|
||||||
|
background: linear-gradient(135deg, var(--login-background-primary), var(--login-background-secondary));
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--space-lg);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-lg, 1.5rem);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ilustration-container h1 {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-xl);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container h1 {
|
||||||
|
font-size: var(--font-xl);
|
||||||
|
margin-bottom: var(--space-md);
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container input[type="text"],
|
||||||
|
.login-form-container input[type="password"] {
|
||||||
|
padding: var(--space-sm);
|
||||||
|
font-size: var(--font-md);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
border: 1px solid var(--color-border-subtle);
|
||||||
|
width: 100%;
|
||||||
|
transition: border-color var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container input:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-primary-light, rgba(0, 123, 255, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container button[type="submit"] {
|
||||||
|
background-color: var(--color-login-button);
|
||||||
|
color: white;
|
||||||
|
padding: var(--space-xs);
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
font-weight: bold;
|
||||||
|
border: none;
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
transition: background-color var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container button[type="submit"]:hover {
|
||||||
|
background-color: var(--color-login-button-focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-fail {
|
||||||
|
color: var(--color-danger);
|
||||||
|
background-color: var(--color-danger-light);
|
||||||
|
border: 1px solid var(--color-danger);
|
||||||
|
padding: var(--space-xs);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
text-align: center;
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-fail i {
|
||||||
|
margin-right: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container p {
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
margin-top: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container a {
|
||||||
|
color: var(--color-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form-container a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
.login-container {
|
||||||
|
flex-direction: column;
|
||||||
|
height: auto;
|
||||||
|
max-width: 95%;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.ilustration-container, .login-form-container {
|
||||||
|
flex: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.ilustration-container {
|
||||||
|
padding: var(--space-md);
|
||||||
|
}
|
||||||
|
.ilustration-container h1 {
|
||||||
|
font-size: var(--font-md);
|
||||||
|
}
|
||||||
|
.login-form-container {
|
||||||
|
padding: var(--space-lg) var(--space-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'animal/css/animal.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
NOVO ANIMAL <br>
|
||||||
|
{% include 'includes/_form.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'animal/css/animal.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
EDITAR ANIMAL <br>
|
||||||
|
{% include 'includes/_form.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
{% 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,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'animal/css/batch.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
NOVO LOTE <br>
|
||||||
|
{% include 'includes/_form.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'animal/css/batch.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
EDITAR LOTE <br>
|
||||||
|
{% include 'includes/_form.html' %}
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="farm_title">
|
||||||
|
<h1>Fazendas</h1>
|
||||||
|
<form method="GET" action="{% url 'entities:farm:farm_home' %}" class="form_farm_search">
|
||||||
|
{% if filter_query %}
|
||||||
|
<a href="{% url 'entities:farm:farm_home' %}" class="btn-clear-filter">{{filter_query}}<i class="fa-solid fa-xmark"></i></a>
|
||||||
|
{% endif %}
|
||||||
|
<label for="farm_search">Buscar</label>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
{% if not farms %}
|
||||||
|
<p>Nenhuma fazenda encontrado.</p>
|
||||||
|
{% else %}
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Nome</th>
|
||||||
|
<th>Área total</th>
|
||||||
|
<th>Editar</th>
|
||||||
|
<th>Excluir</th>
|
||||||
|
</tr>
|
||||||
|
{% for farm in farms %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ farm.name }}</td>
|
||||||
|
<td>{{ farm.total_area }} m2</td>
|
||||||
|
<td><a href="{% url 'entities:farm:farm_update' farm.id %}"><i class="fa-solid fa-pen-to-square"></i></a></td>
|
||||||
|
<td>
|
||||||
|
<form method="POST" action="{% url 'entities:farm:farm_delete' farm.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,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
New - Farm
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Update - Farm
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
New - Paddock
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Delete - Paddock
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
{% 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 %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
New - Paddock Group
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Delete - Paddock Group
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Home - Rotation
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
New - Rotation
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load static %}
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'farm/css/home.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
Delete - Rotation
|
||||||
|
{% endblock %}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from django.urls import include, path
|
||||||
|
|
||||||
|
app_name = "entities"
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("animal/", include("entities.urls.animal", namespace='animal')),
|
||||||
|
path("farm/", include("entities.urls.farm", namespace='farm')),
|
||||||
|
path("rotation/", include("entities.urls.rotation", namespace='rotation')),
|
||||||
|
path("user/", include("entities.urls.user", namespace='user')),
|
||||||
|
]
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from ..views import animal as views
|
||||||
|
|
||||||
|
app_name = 'animal'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.animal_home, name='animal_home'),
|
||||||
|
path('batch/', views.batch_home, name='batch_home'),
|
||||||
|
|
||||||
|
path('new/', views.animal_new, name='animal_new'),
|
||||||
|
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/update/<int:batch_id>', views.batch_update, name='batch_update'),
|
||||||
|
path('batch/delete/<int:batch_id>', views.batch_delete, name='batch_delete'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from ..views import farm as views
|
||||||
|
|
||||||
|
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('new/', views.farm_new, name='farm_new'),
|
||||||
|
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/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/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'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
from ..views import rotation as views
|
||||||
|
|
||||||
|
app_name = 'rotation'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.rotation_home, name='rotation_home'),
|
||||||
|
|
||||||
|
path('new/', views.rotation_new, name='rotation_new'),
|
||||||
|
path('delete/<int:rotation_id>', views.rotation_delete, name='rotation_delete'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from ..views import user as views
|
||||||
|
|
||||||
|
app_name = 'user'
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.user_profile, name='user_profile'),
|
||||||
|
path('login/', views.user_login, name='user_login'),
|
||||||
|
path('logout/', views.user_logout, name='user_logout'),
|
||||||
|
path('signin/', views.user_signin, name='user_signin'),
|
||||||
|
|
||||||
|
path('farm-select/', views.farm_select, name='farm_select'),
|
||||||
|
path('farm-set/<int:farm_id>/', views.farm_set, name='farm_set'),
|
||||||
|
path('farm-set/', views.farm_set, name='farm_set_post'),
|
||||||
|
path('farm-deselect/', views.farm_deselect, name='farm_deselect'),
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from .animal import *
|
||||||
|
from .farm import *
|
||||||
|
from .rotation import *
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
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':
|
||||||
|
context = services.get_animal_new_context()
|
||||||
|
return render(request, 'animal/animal_new.html', context)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
success = services.create_animal(request)
|
||||||
|
if success:
|
||||||
|
return redirect('entities:animal:animal_home')
|
||||||
|
|
||||||
|
return redirect('entities:animal:animal_new')
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def animal_update(request, animal_id):
|
||||||
|
result = services.update_animal(request, animal_id)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'animal/animal_update.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def animal_delete(request, animal_id):
|
||||||
|
services.delete_animal(request, animal_id)
|
||||||
|
return redirect('entities:animal:animal_home')
|
||||||
|
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'batch/batch_new.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def batch_update(request, batch_id):
|
||||||
|
result = services.update_batch(request, batch_id)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'batch/batch_update.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def batch_delete(request, batch_id):
|
||||||
|
services.delete_batch(batch_id)
|
||||||
|
return redirect('entities:animal:batch_home')
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
from django.views.decorators.http import require_http_methods
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from core.decorators import farm_required
|
||||||
|
from ..services import farm as services
|
||||||
|
|
||||||
|
# Farm Views
|
||||||
|
@farm_required
|
||||||
|
def farm_home(request):
|
||||||
|
context = services.get_farm_home_context(request)
|
||||||
|
return render(request, 'farm/farm_home.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def farm_new(request):
|
||||||
|
if request.method == 'GET':
|
||||||
|
context = services.get_farm_new_context()
|
||||||
|
return render(request, 'farm/farm_new.html', context)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
success = services.create_farm(request)
|
||||||
|
if success:
|
||||||
|
return redirect('entities:farm:farm_home')
|
||||||
|
|
||||||
|
return redirect('entities:farm:farm_new')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def farm_update(request, farm_id):
|
||||||
|
result = services.update_farm(request, farm_id)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'farm/farm_update.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def farm_delete(request, farm_id):
|
||||||
|
services.delete_farm(request, farm_id)
|
||||||
|
return redirect('entities:farm:farm_home')
|
||||||
|
|
||||||
|
|
||||||
|
# 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':
|
||||||
|
context = services.get_paddock_new_context()
|
||||||
|
return render(request, 'paddock/paddock_new.html', context)
|
||||||
|
|
||||||
|
elif request.method == 'POST':
|
||||||
|
success = services.create_paddock(request)
|
||||||
|
if success:
|
||||||
|
return redirect('entities:farm:paddock_home')
|
||||||
|
|
||||||
|
return redirect('entities:farm:paddock_new')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def paddock_update(request, paddock_id):
|
||||||
|
result = services.update_paddock(request, paddock_id)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'paddock/paddock_update.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
@require_http_methods(["POST"])
|
||||||
|
def paddock_delete(request, paddock_id):
|
||||||
|
services.delete_paddock(request, paddock_id)
|
||||||
|
return redirect('entities:farm:paddock_home')
|
||||||
|
|
||||||
|
|
||||||
|
# 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':
|
||||||
|
context = services.get_paddock_group_new_context()
|
||||||
|
return render(request, 'paddock_group/paddock_group_new.html', context)
|
||||||
|
|
||||||
|
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_new')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
def paddock_group_update(request, paddock_group_id):
|
||||||
|
result = services.update_paddock_group(request, paddock_group_id)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'paddock_group/paddock_group_update.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
@farm_required
|
||||||
|
@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')
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
def rotation_home(request):
|
||||||
|
return render(request, 'rotation/rotation_home.html')
|
||||||
|
|
||||||
|
def rotation_new(request):
|
||||||
|
return render(request, 'rotation/rotation_new.html')
|
||||||
|
|
||||||
|
def rotation_delete(request):
|
||||||
|
return render(request, 'rotation/rotation_delete.html')
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from ..services import user as services
|
||||||
|
|
||||||
|
|
||||||
|
def user_profile(request): # TO DO
|
||||||
|
return services.user_profile(request)
|
||||||
|
|
||||||
|
|
||||||
|
def user_login(request):
|
||||||
|
result = services.user_login(request)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'login.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
def user_logout(request):
|
||||||
|
return services.user_logout(request)
|
||||||
|
|
||||||
|
|
||||||
|
def user_signin(request): # TO DO
|
||||||
|
return services.user_signin(request)
|
||||||
|
|
||||||
|
|
||||||
|
def farm_select(request):
|
||||||
|
result = services.farm_select(request)
|
||||||
|
|
||||||
|
if result.get('redirect'):
|
||||||
|
return redirect(result['redirect'])
|
||||||
|
|
||||||
|
return render(request, 'farm_select.html', result['context'])
|
||||||
|
|
||||||
|
|
||||||
|
def farm_set(request, farm_id=None):
|
||||||
|
return services.farm_set(request, farm_id)
|
||||||
|
|
||||||
|
|
||||||
|
def farm_deselect(request):
|
||||||
|
return services.farm_deselect(request)
|
||||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ReportConfig(AppConfig):
|
||||||
|
name = 'report'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
|
||||||
|
app_name = 'report'
|
||||||
|
|
||||||
|
@login_required(login_url='/user/login/')
|
||||||
|
def home(request):
|
||||||
|
return HttpResponse("Home app page")
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', home),
|
||||||
|
]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'multigranja.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Whitespace-only changes.
@@ -0,0 +1,7 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'multigranja.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
sys.path.append(str(BASE_DIR / "apps"))
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = 'django-insecure-aziq*utn^c^-nha$#r4veem)7iew70x47zo!9wfsb(y)pav_6v'
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
|
'django.contrib.contenttypes',
|
||||||
|
'django.contrib.sessions',
|
||||||
|
'django.contrib.messages',
|
||||||
|
'django.contrib.staticfiles',
|
||||||
|
# My apps
|
||||||
|
'core',
|
||||||
|
'entities',
|
||||||
|
'report',
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
# My middlewares
|
||||||
|
'core.middlewares.LoginRequiredMiddleware',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Defines the system login URL
|
||||||
|
LOGIN_URL = '/user/login/'
|
||||||
|
# Defines URL paths that do not require login
|
||||||
|
LOGIN_EXEMPT_PATHS = [
|
||||||
|
'/user/login/',
|
||||||
|
'/user/signin/',
|
||||||
|
]
|
||||||
|
# Defines prefixes for URL paths that do not require login
|
||||||
|
LOGIN_EXEMPT_PREFIX = [
|
||||||
|
'/admin/',
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = 'multigranja.urls'
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [BASE_DIR/'templates'],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': [
|
||||||
|
'django.template.context_processors.request',
|
||||||
|
'django.contrib.auth.context_processors.auth',
|
||||||
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
# My processors
|
||||||
|
'core.context_processors.selected_farm_processor',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = 'multigranja.wsgi.application'
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
|
||||||
|
|
||||||
|
# DATABASES = {
|
||||||
|
# 'default': {
|
||||||
|
# 'ENGINE': 'django.db.backends.postgresql',
|
||||||
|
# 'NAME': 'multifarmdb',
|
||||||
|
# 'USER': 'postgres',
|
||||||
|
# 'PASSWORD': 'root',
|
||||||
|
# 'HOST': 'localhost',
|
||||||
|
# 'PORT': '5432',
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.sqlite3',
|
||||||
|
'NAME': BASE_DIR / 'db.sqlite3',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'entities.User'
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = 'pt-br'
|
||||||
|
|
||||||
|
TIME_ZONE = 'America/Sao_Paulo'
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.2/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = 'static/'
|
||||||
|
STATIC_DIR = [BASE_DIR / "static"]
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
BASE_DIR / "static",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
def home(request):
|
||||||
|
return redirect('entities:user:user_login')
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('admin/', admin.site.urls),
|
||||||
|
path('', home),
|
||||||
|
path('', include('entities.urls')),
|
||||||
|
path('report/', include('report.urls')),
|
||||||
|
]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for multigranja project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'multigranja.settings')
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* File: static/css/base/reset.css
|
||||||
|
* Purpose: Normalize and reset default browser styles.
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul, ol {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
img,
|
||||||
|
picture,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
:root {
|
||||||
|
/* SPECIFIC COLORS */
|
||||||
|
--login-background-primary: #343a40;
|
||||||
|
--login-background-secondary: #6b7480;
|
||||||
|
--color-login-button: #343a40;
|
||||||
|
--color-login-button-focus: #6b7480;
|
||||||
|
--color-farm-select-hover: #343a40;
|
||||||
|
--color-farm-select-info: #6d6d6d;
|
||||||
|
--color-new-farm-button: #343a40;
|
||||||
|
--color-new-farm-hover: #6b7480;
|
||||||
|
|
||||||
|
/* GENERIC COLORS */
|
||||||
|
--color-simple-light-gray: #ccc;
|
||||||
|
--color-border-subtle: #ccc;
|
||||||
|
|
||||||
|
/* PRIMARY COLORS */
|
||||||
|
--color-primary: #007bff;
|
||||||
|
--color-secondary: #6c757d;
|
||||||
|
--color-success: #28a745;
|
||||||
|
--color-danger: #dc3545;
|
||||||
|
--color-danger-light: #f8d7da;
|
||||||
|
--color-warn: #dca235;
|
||||||
|
|
||||||
|
/* BACKGROUND/TEXT COLORS */
|
||||||
|
--color-bg-light: #f0f0f0;
|
||||||
|
--color-bg-white: #fafafa;
|
||||||
|
--color-bg-dark: #343a40;
|
||||||
|
--color-bg-sidebar: #343a40;
|
||||||
|
--color-text-dark: #151422;
|
||||||
|
--color-text-light: #f8f9fa;
|
||||||
|
|
||||||
|
/* SPACING DIMENSIONS */
|
||||||
|
--space-xs: 8px;
|
||||||
|
--space-sm: 12px;
|
||||||
|
--space-md: 16px;
|
||||||
|
--space-lg: 24px;
|
||||||
|
--space-xl: 32px;
|
||||||
|
--header-height: 60px;
|
||||||
|
|
||||||
|
/* FONT DIMENSIONS */
|
||||||
|
--font-xs: 12px;
|
||||||
|
--font-sm: 14px;
|
||||||
|
--font-md: 16px;
|
||||||
|
--font-lg: 24px;
|
||||||
|
--font-xl: 32px;
|
||||||
|
|
||||||
|
--border-radius-sm: 4px;
|
||||||
|
--transition-speed: 0.4s;
|
||||||
|
--transition-speed-long: 1.0s;
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/* BUTTON STYLES */
|
||||||
|
.btn-simple {
|
||||||
|
color: var(--color-text-dark);
|
||||||
|
padding: 0 var(--space-xs);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-simple:hover {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
background-color: var(--color-bg-dark);
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* PRIMARY BUTTON (Main Action) */
|
||||||
|
.btn-primary {
|
||||||
|
background-color: var(--color-primary);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
color: var(--color-text-light); /* White text */
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background-color: #0056b3; /* Darker blue on hover (a specific dark value often works best) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SECONDARY BUTTON (Neutral Action) */
|
||||||
|
.btn-secondary {
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
padding: var(--space-sm);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
background-color: #5a6268;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SUCCESS/DANGER BUTTONS */
|
||||||
|
.btn-success {
|
||||||
|
background-color: var(--color-success);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: var(--color-danger);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HOVER EFFECTS */
|
||||||
|
.highlight-hover:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
Whitespace-only changes.
@@ -0,0 +1,151 @@
|
|||||||
|
/* DEFAULTS */
|
||||||
|
body {
|
||||||
|
background-color: var(--color-bg-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.w33 {
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SIDE BAR */
|
||||||
|
.sidebar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 250px;
|
||||||
|
background-color: var(--color-bg-sidebar);
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-brand {
|
||||||
|
text-align: center;
|
||||||
|
padding: var(--space-md);
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: var(--font-lg);
|
||||||
|
display: block;
|
||||||
|
margin-bottom: var(--space-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-menu {
|
||||||
|
margin: var(--space-sm) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
font-size: var(--font-sm);
|
||||||
|
margin-top: var(--space-xs);
|
||||||
|
padding: var(--space-xs) 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-link {
|
||||||
|
color: var(--color-text-light);
|
||||||
|
padding: var(--space-xs) var(--space-xs);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity var(--transition-speed-long) cubic-bezier(0.2, 0.85, 0.75, 1.7), transform var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background-color: rgba(255, 255, 255, 0.15);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
box-shadow: inset -1px 0 0 0 rgba(255, 255, 255, 0.2), inset 0 -1px 0 0 rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* SIDEBAR TOGGLING */
|
||||||
|
|
||||||
|
body.sidebar-collapsed .sidebar {
|
||||||
|
transform: translateX(-250px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed .sidebar-link {
|
||||||
|
opacity: -20;
|
||||||
|
transform: translateX(260px);
|
||||||
|
}
|
||||||
|
|
||||||
|
body.sidebar-collapsed .navbar,
|
||||||
|
body.sidebar-collapsed .main-content {
|
||||||
|
transform: translateX(0);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar,
|
||||||
|
.navbar,
|
||||||
|
.main-content {
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NAV BAR */
|
||||||
|
.navbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(250px);
|
||||||
|
height: var(--header-height);
|
||||||
|
width: calc(100% - 250px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--space-md);
|
||||||
|
z-index: 900;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sidebar-toggle {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inicio do css do seletor antigo */
|
||||||
|
.navbar-farm-selector {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
cursor: default;
|
||||||
|
opacity: 0.75;
|
||||||
|
transition: all var(--transition-speed) ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navbar-farm-selector:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.farm-name {
|
||||||
|
font-size: var(--font-md);
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
/* Fim do css do seletor antigo */
|
||||||
|
|
||||||
|
.navbar-user-info {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--space-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.username {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* MAIN CONTENT */
|
||||||
|
.main-content {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--header-height);
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
transform: translateX(250px);
|
||||||
|
padding: var(--space-lg);
|
||||||
|
width: calc(100% - 250px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -0,0 +1,19 @@
|
|||||||
|
// SIDEBAR TOGGLER
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
const button = document.getElementById("sidebar-toggle");
|
||||||
|
const body = document.body;
|
||||||
|
|
||||||
|
if (!button) return;
|
||||||
|
|
||||||
|
const savedState = localStorage.getItem("sidebarCollapsed");
|
||||||
|
if (savedState === "true") {
|
||||||
|
body.classList.add("sidebar-collapsed");
|
||||||
|
}
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
body.classList.toggle("sidebar-collapsed");
|
||||||
|
|
||||||
|
const isCollapsed = body.classList.contains("sidebar-collapsed");
|
||||||
|
localStorage.setItem("sidebarCollapsed", isCollapsed);
|
||||||
|
});
|
||||||
|
});
|
||||||
Binary file not shown.
Reference in new issue
Block a user