add djls-django crate (#6)

* add djls-django crate

* rework

* oops

* add check for GDAL and GeoDjango

* lots of things

* remove unused scripts

* move scripts to dedicated mod and make static consts

* inline gdal check

* rename mod

* rename mod

* move server info to consts

* adjust pyproject

* hide rustfmt config

* simplify django setup

* adjust printing
This commit is contained in:
Josh Thomas 2024-12-07 16:02:48 -06:00 committed by GitHub
parent b7a1de98dd
commit fce343f44d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 1139 additions and 291 deletions

View file

@ -0,0 +1,12 @@
[package]
name = "djls-django"
version = "0.0.0"
edition = "2021"
[dependencies]
djls-python = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = "2.0.4"

View file

@ -0,0 +1,64 @@
use djls_python::{Python, RunnerError, ScriptRunner};
use serde::Deserialize;
use std::fmt;
use crate::scripts;
#[derive(Debug)]
pub struct App(String);
impl App {
pub fn name(&self) -> &str {
&self.0
}
}
impl fmt::Display for App {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Default)]
pub struct Apps(Vec<App>);
#[derive(Debug, Deserialize)]
struct InstalledAppsCheck {
has_app: bool,
}
impl ScriptRunner for InstalledAppsCheck {
const SCRIPT: &'static str = scripts::INSTALLED_APPS_CHECK;
}
impl Apps {
pub fn from_strings(apps: Vec<String>) -> Self {
Self(apps.into_iter().map(App).collect())
}
pub fn apps(&self) -> &[App] {
&self.0
}
pub fn has_app(&self, name: &str) -> bool {
self.0.iter().any(|app| app.0 == name)
}
pub fn iter(&self) -> impl Iterator<Item = &App> {
self.0.iter()
}
pub fn check_installed(py: &Python, app: &str) -> Result<bool, RunnerError> {
let result = InstalledAppsCheck::run_with_py_args(py, app)?;
Ok(result.has_app)
}
}
impl fmt::Display for Apps {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for app in &self.0 {
writeln!(f, " {}", app)?;
}
Ok(())
}
}

View file

@ -0,0 +1,120 @@
use crate::apps::Apps;
use crate::gis::{check_gis_setup, GISError};
use crate::scripts;
use crate::templates::TemplateTag;
use djls_python::{ImportCheck, Python, RunnerError, ScriptRunner};
use serde::Deserialize;
use std::fmt;
#[derive(Debug)]
pub struct DjangoProject {
py: Python,
settings_module: String,
installed_apps: Apps,
templatetags: Vec<TemplateTag>,
}
#[derive(Debug, Deserialize)]
struct DjangoSetup {
installed_apps: Vec<String>,
templatetags: Vec<TemplateTag>,
}
impl ScriptRunner for DjangoSetup {
const SCRIPT: &'static str = scripts::DJANGO_SETUP;
}
impl DjangoProject {
fn new(
py: Python,
settings_module: String,
installed_apps: Apps,
templatetags: Vec<TemplateTag>,
) -> Self {
Self {
py,
settings_module,
installed_apps,
templatetags,
}
}
pub fn setup() -> Result<Self, ProjectError> {
let settings_module =
std::env::var("DJANGO_SETTINGS_MODULE").expect("DJANGO_SETTINGS_MODULE must be set");
let py = Python::initialize()?;
let has_django = ImportCheck::check(&py, "django")?;
if !has_django {
return Err(ProjectError::DjangoNotFound);
}
if !check_gis_setup(&py)? {
eprintln!("Warning: GeoDjango detected but GDAL is not available.");
eprintln!("Django initialization will be skipped. Some features may be limited.");
eprintln!("To enable full functionality, please install GDAL and other GeoDjango prerequisites.");
return Ok(Self {
py,
settings_module,
installed_apps: Apps::default(),
templatetags: Vec::new(),
});
}
let setup = DjangoSetup::run_with_py(&py)?;
Ok(Self::new(
py,
settings_module,
Apps::from_strings(setup.installed_apps.to_vec()),
setup.templatetags.to_vec(),
))
}
pub fn py(&self) -> &Python {
&self.py
}
fn settings_module(&self) -> &String {
&self.settings_module
}
}
impl fmt::Display for DjangoProject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "Django Project")?;
writeln!(f, "Settings Module: {}", self.settings_module)?;
writeln!(f, "Installed Apps:")?;
write!(f, "{}", self.installed_apps)?;
writeln!(f, "Template Tags:")?;
for tag in &self.templatetags {
write!(f, "{}", tag)?;
writeln!(f)?;
}
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum ProjectError {
#[error("Django is not installed or cannot be imported")]
DjangoNotFound,
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("GIS error: {0}")]
Gis(#[from] GISError),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error(transparent)]
Python(#[from] djls_python::PythonError),
#[error(transparent)]
Runner(#[from] RunnerError),
}

View file

@ -0,0 +1,26 @@
use crate::apps::Apps;
use djls_python::{Python, RunnerError};
use std::process::Command;
pub fn check_gis_setup(py: &Python) -> Result<bool, GISError> {
let has_geodjango = Apps::check_installed(py, "django.contrib.gis")?;
let gdal_is_installed = Command::new("gdalinfo")
.arg("--version")
.output()
.map(|output| output.status.success())
.unwrap_or(false);
Ok(!has_geodjango || gdal_is_installed)
}
#[derive(Debug, thiserror::Error)]
pub enum GISError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("JSON parsing error: {0}")]
Json(#[from] serde_json::Error),
#[error(transparent)]
Runner(#[from] RunnerError),
}

View file

@ -0,0 +1,7 @@
mod apps;
mod django;
mod gis;
mod scripts;
mod templates;
pub use django::DjangoProject;

View file

@ -0,0 +1,4 @@
use djls_python::include_script;
pub const DJANGO_SETUP: &str = include_script!("django_setup");
pub const INSTALLED_APPS_CHECK: &str = include_script!("installed_apps_check");

View file

@ -0,0 +1,30 @@
use serde::Deserialize;
use std::fmt;
#[derive(Clone, Debug, Deserialize)]
pub struct TemplateTag {
name: String,
library: String,
doc: Option<String>,
}
impl fmt::Display for TemplateTag {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let library = if self.library.is_empty() {
"builtins"
} else {
&self.library
};
write!(f, "{} ({})", self.name, library)?;
writeln!(f)?;
if let Some(doc) = &self.doc {
for line in doc.trim_end().split("\n") {
writeln!(f, "{}", line)?;
}
}
Ok(())
}
}