Search: Add landscape/square filters, and "show filters" command #2169
This commit is contained in:
parent
0427163295
commit
7291c1d703
|
@ -10,6 +10,7 @@ var ShowCommand = cli.Command{
|
|||
Usage: "Configuration and system report subcommands",
|
||||
Subcommands: []cli.Command{
|
||||
ShowConfigCommand,
|
||||
ShowFiltersCommand,
|
||||
ShowFormatsCommand,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
|
||||
var ShowConfigCommand = cli.Command{
|
||||
Name: "config",
|
||||
Usage: "Displays global configuration values",
|
||||
Usage: "Shows global configuration values",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-wrap, n",
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/capture"
|
||||
)
|
||||
|
||||
func TestConfigCommand(t *testing.T) {
|
||||
var err error
|
||||
|
||||
ctx := config.CliTestContext()
|
||||
|
||||
output := capture.Output(func() {
|
||||
err = ShowConfigCommand.Run(ctx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expected config command output.
|
||||
assert.Contains(t, output, "config-file")
|
||||
assert.Contains(t, output, "darktable-cli")
|
||||
assert.Contains(t, output, "originals-path")
|
||||
assert.Contains(t, output, "import-path")
|
||||
assert.Contains(t, output, "cache-path")
|
||||
assert.Contains(t, output, "assets-path")
|
||||
}
|
40
internal/commands/show_filters.go
Normal file
40
internal/commands/show_filters.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/report"
|
||||
)
|
||||
|
||||
var ShowFiltersCommand = cli.Command{
|
||||
Name: "filters",
|
||||
Usage: "Displays a search filter overview with examples",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "no-wrap, n",
|
||||
Usage: "disable text-wrapping so the output can be pasted into Markdown files",
|
||||
},
|
||||
},
|
||||
Action: showFiltersAction,
|
||||
}
|
||||
|
||||
// showFiltersAction lists supported search filters.
|
||||
func showFiltersAction(ctx *cli.Context) error {
|
||||
rows, cols := form.Table(&form.SearchPhotos{})
|
||||
|
||||
sort.Slice(rows, func(i, j int) bool {
|
||||
if rows[i][1] == rows[j][1] {
|
||||
return rows[i][0] < rows[j][0]
|
||||
} else {
|
||||
return rows[i][1] < rows[j][1]
|
||||
}
|
||||
})
|
||||
|
||||
fmt.Println(report.Markdown(rows, cols, !ctx.Bool("no-wrap")))
|
||||
|
||||
return nil
|
||||
}
|
|
@ -11,7 +11,7 @@ import (
|
|||
|
||||
var ShowFormatsCommand = cli.Command{
|
||||
Name: "formats",
|
||||
Usage: "Displays supported media and sidecar file formats",
|
||||
Usage: "Lists supported media and sidecar file formats",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "compact, c",
|
||||
|
|
74
internal/commands/show_test.go
Normal file
74
internal/commands/show_test.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/capture"
|
||||
)
|
||||
|
||||
func TestShowConfigCommand(t *testing.T) {
|
||||
var err error
|
||||
|
||||
ctx := config.CliTestContext()
|
||||
|
||||
output := capture.Output(func() {
|
||||
err = ShowConfigCommand.Run(ctx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expected config command output.
|
||||
assert.Contains(t, output, "config-file")
|
||||
assert.Contains(t, output, "darktable-cli")
|
||||
assert.Contains(t, output, "originals-path")
|
||||
assert.Contains(t, output, "import-path")
|
||||
assert.Contains(t, output, "cache-path")
|
||||
assert.Contains(t, output, "assets-path")
|
||||
}
|
||||
|
||||
func TestShowFiltersCommand(t *testing.T) {
|
||||
var err error
|
||||
|
||||
ctx := config.CliTestContext()
|
||||
|
||||
output := capture.Output(func() {
|
||||
err = ShowFiltersCommand.Run(ctx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expected config command output.
|
||||
assert.Contains(t, output, "landscape")
|
||||
assert.Contains(t, output, "live")
|
||||
assert.Contains(t, output, "Examples")
|
||||
assert.Contains(t, output, "Filter")
|
||||
assert.Contains(t, output, "Notes")
|
||||
}
|
||||
|
||||
func TestShowFormatsCommand(t *testing.T) {
|
||||
var err error
|
||||
|
||||
ctx := config.CliTestContext()
|
||||
|
||||
output := capture.Output(func() {
|
||||
err = ShowFormatsCommand.Run(ctx)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Expected config command output.
|
||||
assert.Contains(t, output, "JPEG")
|
||||
assert.Contains(t, output, "MP4")
|
||||
assert.Contains(t, output, "Image")
|
||||
assert.Contains(t, output, "Format")
|
||||
assert.Contains(t, output, "Description")
|
||||
}
|
|
@ -133,7 +133,7 @@ var FileFixtures = FileMap{
|
|||
FileHeight: 0,
|
||||
FileOrientation: 0,
|
||||
FileProjection: "",
|
||||
FileAspectRatio: 0,
|
||||
FileAspectRatio: 1,
|
||||
FileMainColor: "",
|
||||
FileColors: "",
|
||||
FileLuminance: "",
|
||||
|
@ -1562,11 +1562,11 @@ var FileFixtures = FileMap{
|
|||
FileMissing: false,
|
||||
FilePortrait: false,
|
||||
FileDuration: 0,
|
||||
FileWidth: 640,
|
||||
FileHeight: 1136,
|
||||
FileWidth: 16000,
|
||||
FileHeight: 16000,
|
||||
FileOrientation: 1,
|
||||
FileProjection: "",
|
||||
FileAspectRatio: 0.56,
|
||||
FileAspectRatio: 1,
|
||||
FileMainColor: "grey",
|
||||
FileColors: "141101110",
|
||||
FileLuminance: "BD9A22751",
|
||||
|
|
|
@ -4,52 +4,55 @@ import "time"
|
|||
|
||||
// SearchGeo represents search form fields for "/api/v1/geo".
|
||||
type SearchGeo struct {
|
||||
Query string `form:"q"`
|
||||
Filter string `form:"filter"`
|
||||
Near string `form:"near"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
Folder string `form:"folder"` // Alias for Path
|
||||
Name string `form:"name"`
|
||||
Title string `form:"title"`
|
||||
Before time.Time `form:"before" time_format:"2006-01-02"`
|
||||
After time.Time `form:"after" time_format:"2006-01-02"`
|
||||
Favorite bool `form:"favorite"`
|
||||
Unsorted bool `form:"unsorted"`
|
||||
Video bool `form:"video"`
|
||||
Photo bool `form:"photo"`
|
||||
Raw bool `form:"raw"`
|
||||
Live bool `form:"live"`
|
||||
Scan bool `form:"scan"`
|
||||
Panorama bool `form:"panorama"`
|
||||
Archived bool `form:"archived"`
|
||||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Review bool `form:"review"`
|
||||
Quality int `form:"quality"`
|
||||
Faces string `form:"faces"` // Find or exclude faces if detected.
|
||||
Lat float32 `form:"lat"`
|
||||
Lng float32 `form:"lng"`
|
||||
S2 string `form:"s2"`
|
||||
Olc string `form:"olc"`
|
||||
Dist uint `form:"dist"`
|
||||
Face string `form:"face"` // UIDs
|
||||
Subject string `form:"subject"` // UIDs
|
||||
Person string `form:"person"` // Alias for Subject
|
||||
Subjects string `form:"subjects"` // Text
|
||||
People string `form:"people"` // Alias for Subjects
|
||||
Keywords string `form:"keywords"`
|
||||
Album string `form:"album"`
|
||||
Albums string `form:"albums"`
|
||||
Country string `form:"country"`
|
||||
Year string `form:"year"` // Moments
|
||||
Month string `form:"month"` // Moments
|
||||
Day string `form:"day"` // Moments
|
||||
Color string `form:"color"`
|
||||
Camera int `form:"camera"`
|
||||
Lens int `form:"lens"`
|
||||
Count int `form:"count" serialize:"-"`
|
||||
Offset int `form:"offset" serialize:"-"`
|
||||
Query string `form:"q"`
|
||||
Filter string `form:"filter"`
|
||||
Near string `form:"near"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
Folder string `form:"folder"` // Alias for Path
|
||||
Name string `form:"name"`
|
||||
Title string `form:"title"`
|
||||
Before time.Time `form:"before" time_format:"2006-01-02"`
|
||||
After time.Time `form:"after" time_format:"2006-01-02"`
|
||||
Favorite bool `form:"favorite"`
|
||||
Unsorted bool `form:"unsorted"`
|
||||
Video bool `form:"video"`
|
||||
Photo bool `form:"photo"`
|
||||
Raw bool `form:"raw"`
|
||||
Live bool `form:"live"`
|
||||
Scan bool `form:"scan"`
|
||||
Panorama bool `form:"panorama"`
|
||||
Portrait bool `form:"portrait"`
|
||||
Landscape bool `form:"landscape"`
|
||||
Square bool `form:"square"`
|
||||
Archived bool `form:"archived"`
|
||||
Public bool `form:"public"`
|
||||
Private bool `form:"private"`
|
||||
Review bool `form:"review"`
|
||||
Quality int `form:"quality"`
|
||||
Faces string `form:"faces"` // Find or exclude faces if detected.
|
||||
Lat float32 `form:"lat"`
|
||||
Lng float32 `form:"lng"`
|
||||
S2 string `form:"s2"`
|
||||
Olc string `form:"olc"`
|
||||
Dist uint `form:"dist"`
|
||||
Face string `form:"face"` // UIDs
|
||||
Subject string `form:"subject"` // UIDs
|
||||
Person string `form:"person"` // Alias for Subject
|
||||
Subjects string `form:"subjects"` // Text
|
||||
People string `form:"people"` // Alias for Subjects
|
||||
Keywords string `form:"keywords"`
|
||||
Album string `form:"album"`
|
||||
Albums string `form:"albums"`
|
||||
Country string `form:"country"`
|
||||
Year string `form:"year"` // Moments
|
||||
Month string `form:"month"` // Moments
|
||||
Day string `form:"day"` // Moments
|
||||
Color string `form:"color"`
|
||||
Camera int `form:"camera"`
|
||||
Lens int `form:"lens"`
|
||||
Count int `form:"count" serialize:"-"`
|
||||
Offset int `form:"offset" serialize:"-"`
|
||||
}
|
||||
|
||||
// GetQuery returns the query parameter as string.
|
||||
|
|
|
@ -79,6 +79,25 @@ func TestGeoSearch(t *testing.T) {
|
|||
assert.Equal(t, uint(0x61a8), form.Dist)
|
||||
assert.Equal(t, float32(33.45343), form.Lat)
|
||||
})
|
||||
t.Run("PortraitLandscapeSquare", func(t *testing.T) {
|
||||
form := &SearchGeo{Query: "portrait:true landscape:yes square:jo"}
|
||||
|
||||
assert.False(t, form.Portrait)
|
||||
assert.False(t, form.Landscape)
|
||||
assert.False(t, form.Square)
|
||||
assert.False(t, form.Panorama)
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.True(t, form.Portrait)
|
||||
assert.True(t, form.Landscape)
|
||||
assert.True(t, form.Square)
|
||||
assert.False(t, form.Panorama)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGeoSearch_Serialize(t *testing.T) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
// SearchPhotos represents search form fields for "/api/v1/photos".
|
||||
type SearchPhotos struct {
|
||||
Query string `form:"q"`
|
||||
Filter string `form:"filter"`
|
||||
Filter string `form:"filter" notes:"-"`
|
||||
UID string `form:"uid"`
|
||||
Type string `form:"type"`
|
||||
Path string `form:"path"`
|
||||
|
@ -16,7 +16,7 @@ type SearchPhotos struct {
|
|||
Filename string `form:"filename"`
|
||||
Original string `form:"original"`
|
||||
Title string `form:"title"`
|
||||
Hash string `form:"hash"`
|
||||
Hash string `form:"hash" example:"hash:2fd4e1c67a2d"`
|
||||
Primary bool `form:"primary"`
|
||||
Stack bool `form:"stack"`
|
||||
Unstacked bool `form:"unstacked"`
|
||||
|
@ -27,6 +27,9 @@ type SearchPhotos struct {
|
|||
Live bool `form:"live"`
|
||||
Scan bool `form:"scan"`
|
||||
Panorama bool `form:"panorama"`
|
||||
Portrait bool `form:"portrait"`
|
||||
Landscape bool `form:"landscape"`
|
||||
Square bool `form:"square"`
|
||||
Error bool `form:"error"`
|
||||
Hidden bool `form:"hidden"`
|
||||
Archived bool `form:"archived"`
|
||||
|
@ -42,35 +45,34 @@ type SearchPhotos struct {
|
|||
Chroma uint8 `form:"chroma"`
|
||||
Diff uint32 `form:"diff"`
|
||||
Mono bool `form:"mono"`
|
||||
Portrait bool `form:"portrait"`
|
||||
Geo bool `form:"geo"`
|
||||
Keywords string `form:"keywords"` // Filter by keyword(s)
|
||||
Label string `form:"label"` // Label name
|
||||
Category string `form:"category"` // Moments
|
||||
Country string `form:"country"` // Moments
|
||||
State string `form:"state"` // Moments
|
||||
Year string `form:"year"` // Moments
|
||||
Month string `form:"month"` // Moments
|
||||
Day string `form:"day"` // Moments
|
||||
Face string `form:"face"` // UIDs
|
||||
Subject string `form:"subject"` // UIDs
|
||||
Person string `form:"person"` // Alias for Subject
|
||||
Subjects string `form:"subjects"` // People names
|
||||
People string `form:"people"` // Alias for Subjects
|
||||
Album string `form:"album"` // Album UIDs or name
|
||||
Albums string `form:"albums"` // Multi search with and/or
|
||||
Color string `form:"color"` // Main color
|
||||
Faces string `form:"faces"` // Find or exclude faces if detected.
|
||||
Quality int `form:"quality"` // Photo quality score
|
||||
Review bool `form:"review"` // Find photos in review
|
||||
Camera string `form:"camera"` // Camera UID or name
|
||||
Lens string `form:"lens"` // Lens UID or name
|
||||
Before time.Time `form:"before" time_format:"2006-01-02"` // Finds images taken before date
|
||||
After time.Time `form:"after" time_format:"2006-01-02"` // Finds images taken after date
|
||||
Count int `form:"count" binding:"required" serialize:"-"` // Result FILE limit
|
||||
Offset int `form:"offset" serialize:"-"` // Result FILE offset
|
||||
Order string `form:"order" serialize:"-"` // Sort order
|
||||
Merged bool `form:"merged" serialize:"-"` // Merge FILES in response
|
||||
Keywords string `form:"keywords"` // Filter by keyword(s)
|
||||
Label string `form:"label"` // Label name
|
||||
Category string `form:"category"` // Moments
|
||||
Country string `form:"country"` // Moments
|
||||
State string `form:"state"` // Moments
|
||||
Year string `form:"year"` // Moments
|
||||
Month string `form:"month"` // Moments
|
||||
Day string `form:"day"` // Moments
|
||||
Face string `form:"face"` // UIDs
|
||||
Subject string `form:"subject"` // UIDs
|
||||
Person string `form:"person"` // Alias for Subject
|
||||
Subjects string `form:"subjects"` // People names
|
||||
People string `form:"people"` // Alias for Subjects
|
||||
Album string `form:"album" notes:"single name with * wildcard"` // Album UIDs or name
|
||||
Albums string `form:"albums" example:"albums:\"South Africa & Birds\"" notes:"can be combined with & and |"` // Multi search with and/or
|
||||
Color string `form:"color"` // Main color
|
||||
Faces string `form:"faces"` // Find or exclude faces if detected.
|
||||
Quality int `form:"quality"` // Photo quality score
|
||||
Review bool `form:"review"` // Find photos in review
|
||||
Camera string `form:"camera" example:"camera:canon"` // Camera UID or name
|
||||
Lens string `form:"lens" example:"lens:ef24"` // Lens UID or name
|
||||
Before time.Time `form:"before" time_format:"2006-01-02" notes:"taken before this date"` // Finds images taken before date
|
||||
After time.Time `form:"after" time_format:"2006-01-02" notes:"taken after this date"` // Finds images taken after date
|
||||
Count int `form:"count" binding:"required" serialize:"-"` // Result FILE limit
|
||||
Offset int `form:"offset" serialize:"-"` // Result FILE offset
|
||||
Order string `form:"order" serialize:"-"` // Sort order
|
||||
Merged bool `form:"merged" serialize:"-"` // Merge FILES in response
|
||||
}
|
||||
|
||||
func (f *SearchPhotos) GetQuery() string {
|
||||
|
|
|
@ -376,6 +376,25 @@ func TestParseQueryString(t *testing.T) {
|
|||
|
||||
assert.True(t, form.Portrait)
|
||||
})
|
||||
t.Run("PortraitLandscapeSquare", func(t *testing.T) {
|
||||
form := &SearchPhotos{Query: "portrait:true landscape:yes square:jo"}
|
||||
|
||||
assert.False(t, form.Portrait)
|
||||
assert.False(t, form.Landscape)
|
||||
assert.False(t, form.Square)
|
||||
assert.False(t, form.Panorama)
|
||||
|
||||
err := form.ParseQueryString()
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.True(t, form.Portrait)
|
||||
assert.True(t, form.Landscape)
|
||||
assert.True(t, form.Square)
|
||||
assert.False(t, form.Panorama)
|
||||
})
|
||||
t.Run("query for geo with uncommon bool value", func(t *testing.T) {
|
||||
form := &SearchPhotos{Query: "geo:*cat"}
|
||||
|
||||
|
|
80
internal/form/table.go
Normal file
80
internal/form/table.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package form
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/sanitize"
|
||||
)
|
||||
|
||||
// Table returns form fields as table rows for reports.
|
||||
func Table(f interface{}) (rows [][]string, cols []string) {
|
||||
cols = []string{"Filter", "Type", "Examples", "Notes"}
|
||||
|
||||
v := reflect.ValueOf(f)
|
||||
|
||||
if v.Kind() == reflect.Ptr {
|
||||
v = v.Elem()
|
||||
}
|
||||
|
||||
if v.Kind() != reflect.Struct {
|
||||
return rows, cols
|
||||
}
|
||||
|
||||
rows = make([][]string, 0, v.NumField())
|
||||
|
||||
// Iterate through all form fields.
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
fieldValue := v.Field(i)
|
||||
fieldName := v.Type().Field(i).Tag.Get("form")
|
||||
fieldInfo := v.Type().Field(i).Tag.Get("serialize")
|
||||
notes := v.Type().Field(i).Tag.Get("notes")
|
||||
|
||||
// Serialize field values as string.
|
||||
if fieldName != "" && fieldName != "q" && fieldInfo != "-" && notes != "-" {
|
||||
example := v.Type().Field(i).Tag.Get("example")
|
||||
typeName := "any"
|
||||
|
||||
switch t := fieldValue.Interface().(type) {
|
||||
case time.Time:
|
||||
typeName = "timestamp"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:\"2022-01-30 15:23:42\"", fieldName)
|
||||
}
|
||||
case int, int8, int16, int32, int64:
|
||||
typeName = "number"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:0 %s:3", fieldName, fieldName)
|
||||
}
|
||||
case uint, uint8, uint16, uint32, uint64:
|
||||
typeName = "number"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:-1 %s:2", fieldName, fieldName)
|
||||
}
|
||||
case float32, float64:
|
||||
typeName = "decimal"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:1.245", fieldName)
|
||||
}
|
||||
case string:
|
||||
typeName = "string"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:\"name\"", fieldName)
|
||||
}
|
||||
case bool:
|
||||
typeName = "switch"
|
||||
if example == "" {
|
||||
example = fmt.Sprintf("%s:yes %s:no", fieldName, fieldName)
|
||||
}
|
||||
default:
|
||||
log.Warnf("failed exporting %T %s", t, sanitize.Token(fieldName))
|
||||
continue
|
||||
}
|
||||
|
||||
rows = append(rows, []string{fieldName, typeName, example, notes})
|
||||
}
|
||||
}
|
||||
|
||||
return rows, cols
|
||||
}
|
|
@ -352,9 +352,13 @@ func searchPhotos(f form.SearchPhotos, resultCols string) (results PhotoResults,
|
|||
s = s.Where("photos.photo_panorama = 1")
|
||||
}
|
||||
|
||||
// Find portraits only?
|
||||
// Find portrait/landscape/square pictures only?
|
||||
if f.Portrait {
|
||||
s = s.Where("files.file_portrait = 1")
|
||||
} else if f.Landscape {
|
||||
s = s.Where("files.file_aspect_ratio > 1.25")
|
||||
} else if f.Square {
|
||||
s = s.Where("files.file_aspect_ratio = 1")
|
||||
}
|
||||
|
||||
if f.Stackable {
|
||||
|
|
|
@ -62,7 +62,7 @@ func TestPhotosFilterFilename(t *testing.T) {
|
|||
})
|
||||
t.Run("1990* or 2790/07/27900704_070228_D6D51B6C.jpg", func(t *testing.T) {
|
||||
var f form.SearchPhotos
|
||||
Db().LogMode(true)
|
||||
// Db().LogMode(true)
|
||||
f.Filename = "1990* or 2790/07/27900704_070228_D6D51B6C.jpg"
|
||||
f.Merged = true
|
||||
|
||||
|
|
|
@ -296,4 +296,32 @@ func TestPhotosQueryPortrait(t *testing.T) {
|
|||
|
||||
assert.Equal(t, len(photos), len(photos0))
|
||||
})
|
||||
t.Run("Landscape", func(t *testing.T) {
|
||||
var f form.SearchPhotos
|
||||
|
||||
f.Query = "landscape:true"
|
||||
f.Merged = true
|
||||
|
||||
photos, _, err := Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 7, len(photos))
|
||||
})
|
||||
t.Run("Square", func(t *testing.T) {
|
||||
var f form.SearchPhotos
|
||||
|
||||
f.Query = "square:true"
|
||||
f.Merged = true
|
||||
|
||||
photos, _, err := Photos(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, 1, len(photos))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -248,6 +248,15 @@ func Geo(f form.SearchGeo) (results GeoResults, err error) {
|
|||
s = s.Where("photos.photo_panorama = 1")
|
||||
}
|
||||
|
||||
// Find portrait/landscape/square pictures only?
|
||||
if f.Portrait {
|
||||
s = s.Where("files.file_portrait = 1")
|
||||
} else if f.Landscape {
|
||||
s = s.Where("files.file_aspect_ratio > 1.25")
|
||||
} else if f.Square {
|
||||
s = s.Where("files.file_aspect_ratio = 1")
|
||||
}
|
||||
|
||||
// Filter by location country?
|
||||
if f.Country != "" {
|
||||
s = s.Where("photos.photo_country IN (?)", SplitOr(strings.ToLower(f.Country)))
|
||||
|
|
|
@ -813,4 +813,56 @@ func TestGeo(t *testing.T) {
|
|||
assert.NotEmpty(t, r.ID)
|
||||
}
|
||||
})
|
||||
t.Run("Panorama", func(t *testing.T) {
|
||||
var f form.SearchGeo
|
||||
|
||||
f.Query = "panorama:true"
|
||||
|
||||
photos, err := Geo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.LessOrEqual(t, 1, len(photos))
|
||||
})
|
||||
t.Run("Portrait", func(t *testing.T) {
|
||||
var f form.SearchGeo
|
||||
|
||||
f.Query = "portrait:true"
|
||||
|
||||
photos, err := Geo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.LessOrEqual(t, 1, len(photos))
|
||||
})
|
||||
t.Run("Landscape", func(t *testing.T) {
|
||||
var f form.SearchGeo
|
||||
|
||||
f.Query = "landscape:true"
|
||||
|
||||
photos, err := Geo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.LessOrEqual(t, 1, len(photos))
|
||||
})
|
||||
t.Run("Square", func(t *testing.T) {
|
||||
var f form.SearchGeo
|
||||
|
||||
f.Query = "square:true"
|
||||
|
||||
photos, err := Geo(f)
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.LessOrEqual(t, 1, len(photos))
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue