From fcb6797c56886a3e6b4a86459236ebd98bafbf32 Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 21 Jun 2010 13:36:18 -0700 Subject: [PATCH 1/5] Data migration management class and automatic discovery mecanism --HG-- branch : dev --- .../DataMigration/DataMigrationTests.cs | 294 ++++++++++++++++++ .../Orchard.Framework.Tests.csproj | 1 + .../Commands/IDataMigrationCommand.cs | 4 + src/Orchard/DataMigration/DataMigration.cs | 5 + .../DataMigration/DataMigrationManager.cs | 124 ++++++++ .../DataMigration/DataMigrationRecord.cs | 7 + .../DefaultDataMigrationGenerator.cs | 11 + src/Orchard/DataMigration/IDataMigration.cs | 5 + .../DataMigration/IDataMigrationGenerator.cs | 9 + .../DataMigration/IDataMigrationManager.cs | 5 + src/Orchard/Orchard.Framework.csproj | 8 + 11 files changed, 473 insertions(+) create mode 100644 src/Orchard.Tests/DataMigration/DataMigrationTests.cs create mode 100644 src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs create mode 100644 src/Orchard/DataMigration/DataMigration.cs create mode 100644 src/Orchard/DataMigration/DataMigrationManager.cs create mode 100644 src/Orchard/DataMigration/DataMigrationRecord.cs create mode 100644 src/Orchard/DataMigration/DefaultDataMigrationGenerator.cs create mode 100644 src/Orchard/DataMigration/IDataMigration.cs create mode 100644 src/Orchard/DataMigration/IDataMigrationGenerator.cs create mode 100644 src/Orchard/DataMigration/IDataMigrationManager.cs diff --git a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs new file mode 100644 index 000000000..e9482b363 --- /dev/null +++ b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs @@ -0,0 +1,294 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Autofac; +using NHibernate; +using NUnit.Framework; +using Orchard.ContentManagement.Records; +using Orchard.Data; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Folders; +using Orchard.Environment.Extensions.Loaders; +using Orchard.Environment.Extensions.Models; +using Orchard.Tests.ContentManagement; +using Orchard.DataMigration; + +namespace Orchard.Tests.DataMigration { + [TestFixture] + public class DataMigrationTests { + private IContainer _container; + private IExtensionManager _manager; + private StubFolders _folders; + private IDataMigrationManager _dataMigrationManager; + private IRepository _repository; + + private ISessionFactory _sessionFactory; + private ISession _session; + + [SetUp] + public void Init() { + Init(Enumerable.Empty()); + } + + public void Init(IEnumerable dataMigrations) { + + var databaseFileName = System.IO.Path.GetTempFileName(); + _sessionFactory = DataUtility.CreateSessionFactory( + databaseFileName, + typeof(DataMigrationRecord), + typeof(ContentItemVersionRecord), + typeof(ContentItemRecord), + typeof(ContentTypeRecord)); + + var builder = new ContainerBuilder(); + _folders = new StubFolders(); + builder.RegisterInstance(_folders).As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterGeneric(typeof(Repository<>)).As(typeof(IRepository<>)); + _session = _sessionFactory.OpenSession(); + builder.RegisterInstance(new DefaultContentManagerTests.TestSessionLocator(_session)).As(); + foreach(var type in dataMigrations) { + builder.RegisterType(type).As(); + } + _container = builder.Build(); + _manager = _container.Resolve(); + _dataMigrationManager = _container.Resolve(); + _repository = _container.Resolve>(); + + } + + public class StubFolders : IExtensionFolders { + public StubFolders() { + Manifests = new Dictionary(); + } + + public IDictionary Manifests { get; set; } + + public IEnumerable AvailableExtensions() { + foreach (var e in Manifests) { + string name = e.Key; + var parseResult = ExtensionFolders.ParseManifest(Manifests[name]); + yield return ExtensionFolders.GetDescriptorForExtension("~/", name, "Module", parseResult); + } + } + } + + public class DataMigrationEmpty : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + } + + public class DataMigration11 : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + } + + public class DataMigration11Create : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + + public int Create() { + return 999; + } + } + + public class DataMigrationCreateCanBeFollowedByUpdates : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + + public int Create() { + return 42; + } + + public int UpdateFrom42() { + return 666; + } + } + + public class DataMigrationSameMigrationClassCanEvolve : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + + public int Create() { + return 999; + } + + public int UpdateFrom42() { + return 666; + } + + public int UpdateFrom666() { + return 999; + } + + } + + public class DataMigrationDependenciesModule1 : IDataMigration { + public string Feature { + get { return "Feature1"; } + } + + public int Create() { + return 999; + } + } + public class DataMigrationDependenciesModule2 : IDataMigration { + public string Feature { + get { return "Feature2"; } + } + + public int Create() { + return 999; + } + } + + public class StubLoaders : ExtensionLoaderBase { + #region Implementation of IExtensionLoader + + public override int Order { + get { return 1; } + } + + public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { + return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; + } + + public override ExtensionEntry Load(ExtensionDescriptor descriptor) { + return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new Type[0] }; + } + + #endregion + } + + [Test] + public void DataMigrationShouldDoNothingIfNoDataMigrationIsProvidedForFeature() { + Init(new Type[] {typeof (DataMigrationEmpty)}); + + _folders.Manifests.Add("Module2", @" +name: Module2 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature +"); + + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(0)); + } + + [Test] + public void DataMigrationShouldDoNothingIfNoUpgradeOrCreateMethodWasFound() { + Init(new Type[] { typeof(DataMigration11) }); + + _folders.Manifests.Add("Module1", @" +name: Module1 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature +"); + + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(0)); + } + + [Test] + public void CreateShouldReturnVersionNumber() { + Init(new Type[] { typeof(DataMigration11Create) }); + + _folders.Manifests.Add("Module1", @" +name: Module1 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature +"); + + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(1)); + Assert.That(_repository.Table.First().Current, Is.EqualTo(999)); + Assert.That(_repository.Table.First().DataMigrationClass, Is.EqualTo("Orchard.Tests.DataMigration.DataMigrationTests+DataMigration11Create")); + } + + [Test] + public void CreateCanBeFollowedByUpdates() { + Init(new Type[] {typeof (DataMigrationCreateCanBeFollowedByUpdates)}); + + _folders.Manifests.Add("Module1", @" +name: Module1 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature +"); + + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(1)); + Assert.That(_repository.Table.First().Current, Is.EqualTo(666)); + } + + [Test] + public void SameMigrationClassCanEvolve() { + Init(new Type[] { typeof(DataMigrationSameMigrationClassCanEvolve) }); + + _folders.Manifests.Add("Module1", @" +name: Module1 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature +"); + _repository.Create(new DataMigrationRecord() { + Current = 42, + DataMigrationClass = "Orchard.Tests.DataMigration.DataMigrationTests+DataMigrationSameMigrationClassCanEvolve" + }); + + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(1)); + Assert.That(_repository.Table.First().Current, Is.EqualTo(999)); + } + + [Test] + public void DependenciesShouldBeUpgradedFirst() { + + Init(new Type[] { typeof(DataMigrationDependenciesModule1), typeof(DataMigrationDependenciesModule2) }); + + _folders.Manifests.Add("Module1", @" +name: Module1 +version: 0.1 +orchardversion: 1 +features: + Feature1: + Description: Feature + Dependencies: Feature2 +"); + + _folders.Manifests.Add("Module2", @" +name: Module2 +version: 0.1 +orchardversion: 1 +features: + Feature2: + Description: Feature +"); + _dataMigrationManager.Upgrade("Feature1"); + Assert.That(_repository.Table.Count(), Is.EqualTo(2)); + Assert.That(_repository.Fetch(d => d.Current == 999).Count(), Is.EqualTo(2)); + Assert.That(_repository.Fetch(d => d.DataMigrationClass == "Orchard.Tests.DataMigration.DataMigrationTests+DataMigrationDependenciesModule1").Count(), Is.EqualTo(1)); + Assert.That(_repository.Fetch(d => d.DataMigrationClass == "Orchard.Tests.DataMigration.DataMigrationTests+DataMigrationDependenciesModule2").Count(), Is.EqualTo(1)); + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index 682e28b16..b6393e39a 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -180,6 +180,7 @@ Code + diff --git a/src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs b/src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs new file mode 100644 index 000000000..ee2338251 --- /dev/null +++ b/src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs @@ -0,0 +1,4 @@ +namespace Orchard.DataMigration.Commands { + public interface IDataMigrationCommand { + } +} diff --git a/src/Orchard/DataMigration/DataMigration.cs b/src/Orchard/DataMigration/DataMigration.cs new file mode 100644 index 000000000..469ec6556 --- /dev/null +++ b/src/Orchard/DataMigration/DataMigration.cs @@ -0,0 +1,5 @@ +namespace Orchard.DataMigration { + public abstract class DataMigration : IDataMigration { + public abstract string Feature { get; } + } +} diff --git a/src/Orchard/DataMigration/DataMigrationManager.cs b/src/Orchard/DataMigration/DataMigrationManager.cs new file mode 100644 index 000000000..035c543de --- /dev/null +++ b/src/Orchard/DataMigration/DataMigrationManager.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using Orchard.Data; +using Orchard.Environment.Extensions; +using Orchard.Environment.Extensions.Models; +using Orchard.Logging; + +namespace Orchard.DataMigration { + /// + /// Reponsible for maintaining the knowledge of data migration in a per tenant table + /// + public class DataMigrationManager : IDataMigrationManager { + private readonly IEnumerable _dataMigrations; + private readonly IRepository _dataMigrationRepository; + private readonly IDataMigrationGenerator _dataMigrationGenerator; + private readonly IExtensionManager _extensionManager; + + public DataMigrationManager( + IEnumerable dataMigrations, + IRepository dataMigrationRepository, + IDataMigrationGenerator dataMigrationGenerator, + IExtensionManager extensionManager) { + _dataMigrations = dataMigrations; + _dataMigrationRepository = dataMigrationRepository; + _dataMigrationGenerator = dataMigrationGenerator; + _extensionManager = extensionManager; + Logger = NullLogger.Instance; + } + + public ILogger Logger { get; set; } + + public void Upgrade(string feature) { + + // proceed with dependent features first, whatever the module it's in + var dependencies = _extensionManager + .AvailableExtensions() + .SelectMany(e => e.Features) + .Where(f => String.Equals(f.Name, feature, StringComparison.OrdinalIgnoreCase)) + .Where(f => f.Dependencies != null) + .SelectMany( f => f.Dependencies ) + .ToList(); + + foreach(var dependency in dependencies) { + Upgrade(dependency); + } + + var migrations = GetDataMigrations(feature); + + // apply update methods to each migration class for the module + foreach ( var migration in migrations ) { + // copy the objet for the Linq query + var tempMigration = migration; + + // get current version for this migration + var dataMigrationRecord = _dataMigrationRepository.Table + .Where(dm => dm.DataMigrationClass == tempMigration.GetType().FullName) + .FirstOrDefault(); + + var updateMethodNameRegex = new Regex(@"^UpdateFrom(?\d+)$", RegexOptions.Compiled); + var current = 0; + if(dataMigrationRecord != null) { + current = dataMigrationRecord.Current; + } + + // do we need to call Create() ? + if(current == 0) { + // try to resolve a Create method + + var createMethod = migration.GetType().GetMethod("Create", BindingFlags.Public | BindingFlags.Instance); + if(createMethod != null && createMethod.ReturnType == typeof(int)) { + current = (int)createMethod.Invoke(migration, new object[0]); + } + else { + var commands = _dataMigrationGenerator.CreateCommands(); + /// TODO: Execute commands and define current version number + } + } + + // update methods might also be called after Create() + var lookupTable = new Dictionary(); + // construct a lookup table with all managed initial versions + foreach(var methodInfo in migration.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance)) { + var match = updateMethodNameRegex.Match(methodInfo.Name); + if(match.Success) { + lookupTable.Add(int.Parse(match.Groups["version"].Value), methodInfo); + } + } + + while(lookupTable.ContainsKey(current)) { + try { + Logger.Information("Applying migration for {0} from version {1}", feature, current); + current = (int)lookupTable[current].Invoke(migration, new object[0]); + } + catch (Exception ex) { + Logger.Error(ex, "An unexpected error orccured while applying migration on {0} from version {1}", feature, current); + } + } + + // if current is 0, it means no upgrade/create method was found or succeeded + if ( current != 0 ) { + if (dataMigrationRecord == null) { + _dataMigrationRepository.Create(new DataMigrationRecord {Current = current, DataMigrationClass = migration.GetType().FullName}); + } + else { + dataMigrationRecord.Current = current; + _dataMigrationRepository.Update(dataMigrationRecord); + } + } + } + } + + /// + /// Returns all the available IDataMigration instances for a specific module + /// + public IEnumerable GetDataMigrations(string feature) { + return _dataMigrations + .Where(dm => String.Equals(dm.Feature, feature, StringComparison.OrdinalIgnoreCase)) + .ToList(); + } + } +} diff --git a/src/Orchard/DataMigration/DataMigrationRecord.cs b/src/Orchard/DataMigration/DataMigrationRecord.cs new file mode 100644 index 000000000..6f84e7d9e --- /dev/null +++ b/src/Orchard/DataMigration/DataMigrationRecord.cs @@ -0,0 +1,7 @@ +namespace Orchard.DataMigration { + public class DataMigrationRecord { + public virtual int Id { get; set; } + public virtual string DataMigrationClass { get; set; } + public virtual int Current { get; set; } + } +} \ No newline at end of file diff --git a/src/Orchard/DataMigration/DefaultDataMigrationGenerator.cs b/src/Orchard/DataMigration/DefaultDataMigrationGenerator.cs new file mode 100644 index 000000000..20efe1c7e --- /dev/null +++ b/src/Orchard/DataMigration/DefaultDataMigrationGenerator.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.DataMigration.Commands; + +namespace Orchard.DataMigration { + public class DefaultDataMigrationGenerator : IDataMigrationGenerator { + public IEnumerable CreateCommands() { + return Enumerable.Empty(); + } + } +} diff --git a/src/Orchard/DataMigration/IDataMigration.cs b/src/Orchard/DataMigration/IDataMigration.cs new file mode 100644 index 000000000..58267f100 --- /dev/null +++ b/src/Orchard/DataMigration/IDataMigration.cs @@ -0,0 +1,5 @@ +namespace Orchard.DataMigration { + public interface IDataMigration : IDependency { + string Feature { get; } + } +} diff --git a/src/Orchard/DataMigration/IDataMigrationGenerator.cs b/src/Orchard/DataMigration/IDataMigrationGenerator.cs new file mode 100644 index 000000000..0b1264da8 --- /dev/null +++ b/src/Orchard/DataMigration/IDataMigrationGenerator.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Orchard.DataMigration.Commands; + +namespace Orchard.DataMigration { + // Builds and runs the representative migration create calls + public interface IDataMigrationGenerator : IDependency { + IEnumerable CreateCommands(); + } +} diff --git a/src/Orchard/DataMigration/IDataMigrationManager.cs b/src/Orchard/DataMigration/IDataMigrationManager.cs new file mode 100644 index 000000000..bf7032f67 --- /dev/null +++ b/src/Orchard/DataMigration/IDataMigrationManager.cs @@ -0,0 +1,5 @@ +namespace Orchard.DataMigration { + public interface IDataMigrationManager : IDependency { + void Upgrade(string feature); + } +} \ No newline at end of file diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index ade1484b9..04c322a64 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -348,6 +348,14 @@ Code + + + + + + + + From 2a8c51128e02dbe1b31742ba74d709168ab9269c Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Mon, 21 Jun 2010 13:44:43 -0700 Subject: [PATCH 2/5] Added upgrade database command --HG-- branch : dev --- .../DataMigration/DataMigrationTests.cs | 18 ----------- .../Commands/DataMigrationCommands.cs | 31 +++++++++++++++++++ .../{Commands => }/IDataMigrationCommand.cs | 2 +- src/Orchard/Orchard.Framework.csproj | 3 +- 4 files changed, 34 insertions(+), 20 deletions(-) create mode 100644 src/Orchard/DataMigration/Commands/DataMigrationCommands.cs rename src/Orchard/DataMigration/{Commands => }/IDataMigrationCommand.cs (51%) diff --git a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs index e9482b363..a12e2e58b 100644 --- a/src/Orchard.Tests/DataMigration/DataMigrationTests.cs +++ b/src/Orchard.Tests/DataMigration/DataMigrationTests.cs @@ -149,24 +149,6 @@ namespace Orchard.Tests.DataMigration { } } - public class StubLoaders : ExtensionLoaderBase { - #region Implementation of IExtensionLoader - - public override int Order { - get { return 1; } - } - - public override ExtensionProbeEntry Probe(ExtensionDescriptor descriptor) { - return new ExtensionProbeEntry { Descriptor = descriptor, Loader = this }; - } - - public override ExtensionEntry Load(ExtensionDescriptor descriptor) { - return new ExtensionEntry { Descriptor = descriptor, ExportedTypes = new Type[0] }; - } - - #endregion - } - [Test] public void DataMigrationShouldDoNothingIfNoDataMigrationIsProvidedForFeature() { Init(new Type[] {typeof (DataMigrationEmpty)}); diff --git a/src/Orchard/DataMigration/Commands/DataMigrationCommands.cs b/src/Orchard/DataMigration/Commands/DataMigrationCommands.cs new file mode 100644 index 000000000..4faca9604 --- /dev/null +++ b/src/Orchard/DataMigration/Commands/DataMigrationCommands.cs @@ -0,0 +1,31 @@ +using System; +using Orchard.Commands; + +namespace Orchard.DataMigration.Commands { + public class DataMigrationCommands : DefaultOrchardCommandHandler { + private readonly IDataMigrationManager _dataMigrationManager; + + public DataMigrationCommands( + IDataMigrationManager dataMigrationManager) { + _dataMigrationManager = dataMigrationManager; + } + + [OrchardSwitch] + public string Feature { get; set; } + + [CommandName("upgrade database")] + [CommandHelp("upgrade database /Feature: \r\n\t" + "Upgrades or create the database tables for the named ")] + [OrchardSwitches("Feature")] + public string UpgradeDatabase() { + try { + _dataMigrationManager.Upgrade(Feature); + } + catch ( Exception ex ) { + Context.Output.WriteLine(T("An error occured while upgrading the database: " + ex.Message)); + return "Upgrade terminated."; + } + + return "Database upgraded"; + } + } +} \ No newline at end of file diff --git a/src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs b/src/Orchard/DataMigration/IDataMigrationCommand.cs similarity index 51% rename from src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs rename to src/Orchard/DataMigration/IDataMigrationCommand.cs index ee2338251..cb56b9c63 100644 --- a/src/Orchard/DataMigration/Commands/IDataMigrationCommand.cs +++ b/src/Orchard/DataMigration/IDataMigrationCommand.cs @@ -1,4 +1,4 @@ -namespace Orchard.DataMigration.Commands { +namespace Orchard.DataMigration { public interface IDataMigrationCommand { } } diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 04c322a64..3ac95b7c7 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -348,7 +348,8 @@ Code - + + From b6841043ce4cbc8cc7b70c311a5a57c58a2dd82f Mon Sep 17 00:00:00 2001 From: Suha Can Date: Mon, 21 Jun 2010 18:23:41 -0700 Subject: [PATCH 3/5] - Dynamic field metadata import/export and part definition storage/alteration. --HG-- branch : dev --- .../Core/Common/Drivers/CommonDriver.cs | 6 +- .../Metadata/ContentDefinitionManager.cs | 41 +++++++++- .../Controllers/MetadataController.cs | 11 +-- .../Builders/ContentPartDefinitionBuilder.cs | 78 ++++++++++++++++++- .../MetaData/IContentDefinitionReader.cs | 1 + .../Services/ContentDefinitionReader.cs | 23 +++++- .../Services/ContentDefinitionWriter.cs | 4 +- 7 files changed, 147 insertions(+), 17 deletions(-) diff --git a/src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs b/src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs index b8078ffce..9def86b45 100644 --- a/src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs +++ b/src/Orchard.Web/Core/Common/Drivers/CommonDriver.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using Orchard.ContentManagement.Drivers; using Orchard.Core.Common.Models; using Orchard.Core.Common.ViewModels; diff --git a/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs b/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs index 7146e2415..0a00eeff9 100644 --- a/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs +++ b/src/Orchard.Web/Core/Settings/Metadata/ContentDefinitionManager.cs @@ -13,16 +13,19 @@ namespace Orchard.Core.Settings.Metadata { public class ContentDefinitionManager : Component, IContentDefinitionManager { private readonly IRepository _typeDefinitionRepository; private readonly IRepository _partDefinitionRepository; + private readonly IRepository _fieldDefinitionRepository; private readonly IMapper> _settingsReader; private readonly IMapper, XElement> _settingsWriter; public ContentDefinitionManager( IRepository typeDefinitionRepository, IRepository partDefinitionRepository, + IRepository fieldDefinitionRepository, IMapper> settingsReader, IMapper, XElement> settingsWriter) { _typeDefinitionRepository = typeDefinitionRepository; _partDefinitionRepository = partDefinitionRepository; + _fieldDefinitionRepository = fieldDefinitionRepository; _settingsReader = settingsReader; _settingsWriter = settingsWriter; } @@ -48,7 +51,7 @@ namespace Orchard.Core.Settings.Metadata { } public void StorePartDefinition(ContentPartDefinition contentPartDefinition) { - throw new NotImplementedException(); + Apply(contentPartDefinition, Acquire(contentPartDefinition)); } private ContentTypeDefinitionRecord Acquire(ContentTypeDefinition contentTypeDefinition) { @@ -69,6 +72,15 @@ namespace Orchard.Core.Settings.Metadata { return result; } + private ContentFieldDefinitionRecord Acquire(ContentFieldDefinition contentFieldDefinition) { + var result = _fieldDefinitionRepository.Fetch(x => x.Name == contentFieldDefinition.Name).SingleOrDefault(); + if (result == null) { + result = new ContentFieldDefinitionRecord { Name = contentFieldDefinition.Name }; + _fieldDefinitionRepository.Create(result); + } + return result; + } + private void Apply(ContentTypeDefinition model, ContentTypeDefinitionRecord record) { record.Settings = _settingsWriter.Map(model.Settings).ToString(); @@ -95,7 +107,34 @@ namespace Orchard.Core.Settings.Metadata { record.Settings = Compose(_settingsWriter.Map(model.Settings)); } + private void Apply(ContentPartDefinition model, ContentPartDefinitionRecord record) { + record.Settings = _settingsWriter.Map(model.Settings).ToString(); + var toRemove = record.ContentPartFieldDefinitionRecords + .Where(fieldDefinitionRecord => !model.Fields.Any(field => fieldDefinitionRecord.ContentFieldDefinitionRecord.Name == field.FieldDefinition.Name)) + .ToList(); + + foreach (var remove in toRemove) { + record.ContentPartFieldDefinitionRecords.Remove(remove); + } + + foreach (var field in model.Fields) { + var fieldName = field.FieldDefinition.Name; + var partFieldRecord = record.ContentPartFieldDefinitionRecords.SingleOrDefault(r => r.ContentFieldDefinitionRecord.Name == fieldName); + if (partFieldRecord == null) { + partFieldRecord = new ContentPartFieldDefinitionRecord { + ContentFieldDefinitionRecord = Acquire(field.FieldDefinition), + Name = field.Name + }; + record.ContentPartFieldDefinitionRecords.Add(partFieldRecord); + } + Apply(field, partFieldRecord); + } + } + + private void Apply(ContentPartDefinition.Field model, ContentPartFieldDefinitionRecord record) { + record.Settings = Compose(_settingsWriter.Map(model.Settings)); + } ContentTypeDefinition Build(ContentTypeDefinitionRecord source) { return new ContentTypeDefinition( diff --git a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs index bc3a60e86..6c8cec838 100644 --- a/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs +++ b/src/Orchard.Web/Modules/Orchard.DevTools/Controllers/MetadataController.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Web; +using System.IO; using System.Web.Mvc; using System.Xml; using System.Xml.Linq; @@ -59,6 +55,11 @@ namespace Orchard.DevTools.Controllers { var typeName = XmlConvert.DecodeName(element.Name.LocalName); _contentDefinitionManager.AlterTypeDefinition(typeName, alteration => _contentDefinitionReader.Merge(typeElement, alteration)); } + foreach (var element in root.Elements("Parts").Elements()) { + var partElement = element; + var partName = XmlConvert.DecodeName(element.Name.LocalName); + _contentDefinitionManager.AlterPartDefinition(partName, alteration => _contentDefinitionReader.Merge(partElement, alteration)); + } return RedirectToAction("Index"); } } diff --git a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs index 15f93026d..ede035d89 100644 --- a/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs +++ b/src/Orchard/ContentManagement/MetaData/Builders/ContentPartDefinitionBuilder.cs @@ -1,10 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Orchard.ContentManagement.MetaData.Models; namespace Orchard.ContentManagement.MetaData.Builders { public class ContentPartDefinitionBuilder { - private readonly string _name; + private string _name; private readonly IList _fields; private readonly IDictionary _settings; @@ -28,10 +29,83 @@ namespace Orchard.ContentManagement.MetaData.Builders { return new ContentPartDefinition(_name, _fields, _settings); } + public ContentPartDefinitionBuilder Named(string name) { + _name = name; + return this; + } + + public ContentPartDefinitionBuilder RemoveField(string fieldName) { + var existingField = _fields.SingleOrDefault(x => x.FieldDefinition.Name == fieldName); + if (existingField != null) { + _fields.Remove(existingField); + } + return this; + } + public ContentPartDefinitionBuilder WithSetting(string name, string value) { _settings[name] = value; return this; } + public ContentPartDefinitionBuilder WithField(string fieldName) { + return WithField(fieldName, configuration => { }); + } + + public ContentPartDefinitionBuilder WithField(string fieldName, Action configuration) { + var fieldDefinition = new ContentFieldDefinition(fieldName); + var existingField = _fields.SingleOrDefault(x => x.FieldDefinition.Name == fieldDefinition.Name); + if (existingField != null) { + _fields.Remove(existingField); + } + else { + existingField = new ContentPartDefinition.Field(fieldDefinition, fieldName, new Dictionary()); + } + var configurer = new FieldConfigurerImpl(existingField); + configuration(configurer); + _fields.Add(configurer.Build()); + return this; + } + + public abstract class FieldConfigurer { + protected readonly IDictionary _settings; + + protected FieldConfigurer(ContentPartDefinition.Field field) { + _settings = field.Settings.ToDictionary(kv => kv.Key, kv => kv.Value); + } + + public FieldConfigurer WithSetting(string name, string value) { + _settings[name] = value; + return this; + } + + public abstract FieldConfigurer OfType(ContentFieldDefinition fieldDefinition); + public abstract FieldConfigurer OfType(string fieldType); + } + + class FieldConfigurerImpl : FieldConfigurer { + private ContentFieldDefinition _fieldDefinition; + private string _fieldName; + + public FieldConfigurerImpl(ContentPartDefinition.Field field) + : base(field) { + _fieldDefinition = field.FieldDefinition; + _fieldName = field.Name; + } + + public ContentPartDefinition.Field Build() { + return new ContentPartDefinition.Field(_fieldDefinition, _fieldName, _settings); + } + + public override FieldConfigurer OfType(ContentFieldDefinition fieldDefinition) { + _fieldDefinition = fieldDefinition; + return this; + } + + public override FieldConfigurer OfType(string fieldType) { + _fieldDefinition = new ContentFieldDefinition(fieldType); + return this; + } + } + } } \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs b/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs index c24691de3..9d5b44123 100644 --- a/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs +++ b/src/Orchard/ContentManagement/MetaData/IContentDefinitionReader.cs @@ -5,6 +5,7 @@ using Orchard.ContentManagement.MetaData.Models; namespace Orchard.ContentManagement.MetaData { public interface IContentDefinitionReader : IDependency { void Merge(XElement source, ContentTypeDefinitionBuilder builder); + void Merge(XElement source, ContentPartDefinitionBuilder builder); } public static class ContentDefinitionReaderExtensions { diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs index ad1037632..400bf1ea4 100644 --- a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionReader.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Xml; using System.Xml.Linq; using Orchard.ContentManagement.MetaData.Builders; @@ -28,5 +27,25 @@ namespace Orchard.ContentManagement.MetaData.Services { }); } } + + public void Merge(XElement source, ContentPartDefinitionBuilder builder) { + builder.Named(XmlConvert.DecodeName(source.Name.LocalName)); + foreach (var setting in _settingsReader.Map(source)) { + builder.WithSetting(setting.Key, setting.Value); + } + + foreach (var iter in source.Elements()) { + var fieldElement = iter; + string[] fieldParameters = fieldElement.Name.LocalName.Split('.'); + builder.WithField( + XmlConvert.DecodeName(fieldParameters[0]), + fieldBuilder => { + foreach (var setting in _settingsReader.Map(fieldElement)) { + fieldBuilder.WithSetting(setting.Key, setting.Value); + } + fieldBuilder.OfType(fieldParameters[1]); + }); + } + } } } \ No newline at end of file diff --git a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs index fc61eb16e..816d4c4a4 100644 --- a/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs +++ b/src/Orchard/ContentManagement/MetaData/Services/ContentDefinitionWriter.cs @@ -24,8 +24,8 @@ namespace Orchard.ContentManagement.MetaData.Services { public XElement Export(ContentPartDefinition partDefinition) { var partElement = NewElement(partDefinition.Name, partDefinition.Settings); foreach(var partField in partDefinition.Fields) { - var partFieldElement = NewElement(partField.Name, partField.Settings); - partFieldElement.SetAttributeValue("FieldType", partField.FieldDefinition.Name); + var attributeName = partField.Name + "." + partField.FieldDefinition.Name; + var partFieldElement = NewElement(attributeName, partField.Settings); partElement.Add(partFieldElement); } return partElement; From d126bc9f3dd584a76d3833f39f4e18a2a9dff908 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Tue, 22 Jun 2010 01:03:15 -0700 Subject: [PATCH 4/5] Changed the Orchard.Indexing project to not fire up Cassini --HG-- branch : dev --- .../Modules/Orchard.Indexing/Orchard.Indexing.csproj | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj b/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj index ce47de856..179e2bb84 100644 --- a/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj +++ b/src/Orchard.Web/Modules/Orchard.Indexing/Orchard.Indexing.csproj @@ -105,9 +105,8 @@ False - False - - + True + http://orchard.codeplex.com False From ab81cda4ebfe8a6ea399d98b85f78e55fd2bccc0 Mon Sep 17 00:00:00 2001 From: Nathan Heskew Date: Tue, 22 Jun 2010 03:36:04 -0700 Subject: [PATCH 5/5] Getting some work towards being able to edit content types in to share the fun. - includes changing settings from IDictionary to SettingsDictionary : IDictionary w/ GetModel - also cleaned up content type creation a little --HG-- branch : dev --- .../Providers/CommonAspectProviderTests.cs | 1 - .../Services/ContentDefinitionReaderTests.cs | 5 +- .../ContentManagement/Models/Phi.cs | 5 +- .../Contents/ContentTypeDefinitionStub.cs | 10 --- .../Contents/Controllers/AdminController.cs | 73 ++++++++++++++++-- src/Orchard.Web/Core/Contents/Permissions.cs | 10 ++- .../Services/ContentDefinitionService.cs | 28 ++++--- .../Services/IContentDefinitionService.cs | 3 +- .../Core/Contents/Styles/admin.css | 6 ++ .../Contents/ViewModels/EditTypeViewModel.cs | 77 +++++++++++++++++++ .../Core/Contents/Views/Admin/EditType.ascx | 23 ++++++ .../ContentTypeDefinition.ascx | 2 +- .../EditorTemplates/ContentTypePart.ascx | 13 ++++ .../Views/EditorTemplates/FieldOnPart.ascx | 14 ++++ .../Views/EditorTemplates/FieldsOnPart.ascx | 10 +++ .../Contents/Views/EditorTemplates/Part.ascx | 21 +++++ .../Contents/Views/EditorTemplates/Parts.ascx | 12 +++ .../Views/EditorTemplates/Settings.ascx | 14 ++++ src/Orchard.Web/Core/Orchard.Core.csproj | 9 ++- .../Metadata/ContentDefinitionManager.cs | 8 +- .../Handlers/ContentItemBuilder.cs | 5 +- .../Builders/ContentPartDefinitionBuilder.cs | 12 +-- .../Builders/ContentTypeDefinitionBuilder.cs | 12 +-- .../MetaData/Models/ContentPartDefinition.cs | 10 +-- .../MetaData/Models/ContentTypeDefinition.cs | 18 +++-- .../MetaData/Models/SettingsDictionary.cs | 24 ++++++ .../Services/ContentDefinitionReader.cs | 8 +- .../Services/ContentDefinitionWriter.cs | 10 +-- .../MetaData/Services/SettingsFormatter.cs | 16 ++-- src/Orchard/Orchard.Framework.csproj | 1 + 30 files changed, 372 insertions(+), 88 deletions(-) delete mode 100644 src/Orchard.Web/Core/Contents/ContentTypeDefinitionStub.cs create mode 100644 src/Orchard.Web/Core/Contents/Styles/admin.css create mode 100644 src/Orchard.Web/Core/Contents/ViewModels/EditTypeViewModel.cs create mode 100644 src/Orchard.Web/Core/Contents/Views/Admin/EditType.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/ContentTypePart.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/FieldOnPart.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/FieldsOnPart.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/Part.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/Parts.ascx create mode 100644 src/Orchard.Web/Core/Contents/Views/EditorTemplates/Settings.ascx create mode 100644 src/Orchard/ContentManagement/MetaData/Models/SettingsDictionary.cs diff --git a/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs b/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs index 210d12203..86b864646 100644 --- a/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs +++ b/src/Orchard.Core.Tests/Common/Providers/CommonAspectProviderTests.cs @@ -16,7 +16,6 @@ using Orchard.ContentManagement.Records; using Orchard.Localization; using Orchard.Security; using Orchard.Tests.Modules; -using Orchard.Mvc.ViewModels; using Orchard.Core.Common.ViewModels; using System.Web.Mvc; diff --git a/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs index 07a363a4d..725f07271 100644 --- a/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs +++ b/src/Orchard.Tests/ContentManagement/MetaData/Services/ContentDefinitionReaderTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Linq; using System.Xml.Linq; using NUnit.Framework; using Orchard.ContentManagement.MetaData; diff --git a/src/Orchard.Tests/ContentManagement/Models/Phi.cs b/src/Orchard.Tests/ContentManagement/Models/Phi.cs index 69bbd39de..719e7aafa 100644 --- a/src/Orchard.Tests/ContentManagement/Models/Phi.cs +++ b/src/Orchard.Tests/ContentManagement/Models/Phi.cs @@ -1,11 +1,10 @@ -using System.Collections.Generic; -using Orchard.ContentManagement; +using Orchard.ContentManagement; using Orchard.ContentManagement.MetaData.Models; namespace Orchard.Tests.ContentManagement.Models { public class Phi : ContentField { public Phi() { - PartFieldDefinition = new ContentPartDefinition.Field(new ContentFieldDefinition("Phi"), "Phi", new Dictionary()); + PartFieldDefinition = new ContentPartDefinition.Field(new ContentFieldDefinition("Phi"), "Phi", new SettingsDictionary()); } } } diff --git a/src/Orchard.Web/Core/Contents/ContentTypeDefinitionStub.cs b/src/Orchard.Web/Core/Contents/ContentTypeDefinitionStub.cs deleted file mode 100644 index 45af29afd..000000000 --- a/src/Orchard.Web/Core/Contents/ContentTypeDefinitionStub.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace Orchard.Core.Contents { - public class ContentTypeDefinitionStub { - [StringLength(128)] - public string Name { get; set; } - [Required, StringLength(1024)] - public string DisplayName { get; set; } - } -} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs b/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs index a2f8dcacf..c2756d441 100644 --- a/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs +++ b/src/Orchard.Web/Core/Contents/Controllers/AdminController.cs @@ -3,12 +3,13 @@ using System.Linq; using System.Web.Mvc; using System.Web.Routing; using Orchard.ContentManagement; -using Orchard.ContentManagement.MetaData; +using Orchard.ContentManagement.MetaData.Models; using Orchard.Core.Contents.Services; using Orchard.Core.Contents.ViewModels; using Orchard.Data; using Orchard.Localization; using Orchard.Logging; +using Orchard.Mvc.Results; using Orchard.Mvc.ViewModels; using Orchard.UI.Notify; @@ -51,19 +52,19 @@ namespace Orchard.Core.Contents.Controllers { }); } - public ActionResult CreateType(CreateTypeViewModel viewModel) { - if (!Services.Authorizer.Authorize(Permissions.CreateContentType, T("Not allowed to create a content type."))) + public ActionResult CreateType() { + if (!Services.Authorizer.Authorize(Permissions.CreateContentTypes, T("Not allowed to create a content type."))) return new HttpUnauthorizedResult(); - return View(viewModel); + return View(new CreateTypeViewModel()); } [HttpPost, ActionName("CreateType")] public ActionResult CreateTypePOST(CreateTypeViewModel viewModel) { - if (!Services.Authorizer.Authorize(Permissions.CreateContentType, T("Not allowed to create a content type."))) + if (!Services.Authorizer.Authorize(Permissions.CreateContentTypes, T("Not allowed to create a content type."))) return new HttpUnauthorizedResult(); - var model = new ContentTypeDefinitionStub(); + var model = new ContentTypeDefinition(""); TryUpdateModel(model); if (!ModelState.IsValid) { @@ -76,9 +77,69 @@ namespace Orchard.Core.Contents.Controllers { return RedirectToAction("Index"); } + public ActionResult EditType(string id) { + if (!Services.Authorizer.Authorize(Permissions.CreateContentTypes, T("Not allowed to edit a content type."))) + return new HttpUnauthorizedResult(); + + var contentTypeDefinition = _contentDefinitionService.GetTypeDefinition(id); + + if (contentTypeDefinition == null) + return new NotFoundResult(); + + return View(new EditTypeViewModel(contentTypeDefinition)); + } + + [HttpPost, ActionName("EditType")] + public ActionResult EditTypePOST(string id) { + if (!Services.Authorizer.Authorize(Permissions.CreateContentTypes, T("Not allowed to edit a content type."))) + return new HttpUnauthorizedResult(); + + var contentTypeDefinition = _contentDefinitionService.GetTypeDefinition(id); + + if (contentTypeDefinition == null) + return new NotFoundResult(); + + var viewModel = new EditTypeViewModel(); + TryUpdateModel(viewModel); + + if (!ModelState.IsValid) { + return EditType(id); + } + + //todo: apply the changes along the lines of but definately not resembling + // for now this _might_ just get a little messy -> + _contentDefinitionService.AlterTypeDefinition( + new ContentTypeDefinition( + viewModel.Name, + viewModel.DisplayName, + viewModel.Parts.Select( + p => new ContentTypeDefinition.Part( + new ContentPartDefinition( + p.PartDefinition.Name, + p.PartDefinition.Fields.Select( + f => new ContentPartDefinition.Field( + new ContentFieldDefinition(f.FieldDefinition.Name), + f.Name, + f.Settings + ) + ), + p.PartDefinition.Settings + ), + p.Settings + ) + ), + viewModel.Settings + ) + ); + // little == lot + + return RedirectToAction("Index"); + } + #endregion #region Content + #endregion public ActionResult List(ListContentsViewModel model) { diff --git a/src/Orchard.Web/Core/Contents/Permissions.cs b/src/Orchard.Web/Core/Contents/Permissions.cs index 8fc85f1ae..ccc2c9641 100644 --- a/src/Orchard.Web/Core/Contents/Permissions.cs +++ b/src/Orchard.Web/Core/Contents/Permissions.cs @@ -3,15 +3,17 @@ using Orchard.Security.Permissions; namespace Orchard.Core.Contents { public class Permissions : IPermissionProvider { - public static readonly Permission CreateContentType = new Permission { Name = "CreateContentType", Description = "Create custom content type." }; + public static readonly Permission CreateContentTypes = new Permission { Name = "CreateContentTypes", Description = "Create custom content types." }; + public static readonly Permission EditContentTypes = new Permission { Name = "EditContentTypes", Description = "Edit content types." }; public string ModuleName { get { return "Contents"; } } public IEnumerable GetPermissions() { - return new Permission[] { - CreateContentType, + return new [] { + CreateContentTypes, + EditContentTypes, }; } @@ -19,7 +21,7 @@ namespace Orchard.Core.Contents { return new[] { new PermissionStereotype { Name = "Administrator", - Permissions = new[] {CreateContentType} + Permissions = GetPermissions() } }; } diff --git a/src/Orchard.Web/Core/Contents/Services/ContentDefinitionService.cs b/src/Orchard.Web/Core/Contents/Services/ContentDefinitionService.cs index 473bebcea..16052287d 100644 --- a/src/Orchard.Web/Core/Contents/Services/ContentDefinitionService.cs +++ b/src/Orchard.Web/Core/Contents/Services/ContentDefinitionService.cs @@ -25,25 +25,30 @@ namespace Orchard.Core.Contents.Services { } public ContentTypeDefinition GetTypeDefinition(string name) { - throw new NotImplementedException(); + return _contentDefinitionManager.GetTypeDefinition(name); } - public void AddTypeDefinition(ContentTypeDefinitionStub definitionStub) { - if (string.IsNullOrWhiteSpace(definitionStub.Name)) - definitionStub.Name = GenerateTypeName(definitionStub.DisplayName); + public void AddTypeDefinition(ContentTypeDefinition contentTypeDefinition) { + var typeName = string.IsNullOrWhiteSpace(contentTypeDefinition.Name) + ? GenerateTypeName(contentTypeDefinition.DisplayName) + : contentTypeDefinition.Name; - while (_contentDefinitionManager.GetTypeDefinition(definitionStub.Name) != null) - definitionStub.Name = VersionTypeName(definitionStub.Name); + while (_contentDefinitionManager.GetTypeDefinition(typeName) != null) + typeName = VersionTypeName(typeName); //just giving the new type some default parts for now _contentDefinitionManager.AlterTypeDefinition( - definitionStub.Name, - cfg => cfg.Named(definitionStub.Name, definitionStub.DisplayName) + typeName, + cfg => cfg.Named(typeName, contentTypeDefinition.DisplayName) .WithPart("CommonAspect") //.WithPart("RoutableAspect") //need to go the new routable route .WithPart("BodyAspect")); + + Services.Notifier.Information(T("Created content type: {0}", contentTypeDefinition.DisplayName)); + } - Services.Notifier.Information(T("Created content type: {0}", definitionStub.DisplayName)); + public void AlterTypeDefinition(ContentTypeDefinition contentTypeDefinition) { + _contentDefinitionManager.StoreTypeDefinition(contentTypeDefinition); } public void RemoveTypeDefinition(string name) { @@ -69,7 +74,7 @@ namespace Orchard.Core.Contents.Services { } private static string VersionTypeName(string name) { - var version = 2; + int version; var nameParts = name.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries); if (nameParts.Length > 1 && int.TryParse(nameParts.Last(), out version)) { @@ -77,6 +82,9 @@ namespace Orchard.Core.Contents.Services { //this could unintentionally chomp something that looks like a version name = string.Join("-", nameParts.Take(nameParts.Length - 1)); } + else { + version = 2; + } return string.Format("{0}-{1}", name, version); } diff --git a/src/Orchard.Web/Core/Contents/Services/IContentDefinitionService.cs b/src/Orchard.Web/Core/Contents/Services/IContentDefinitionService.cs index c4f996bd9..157e53f3a 100644 --- a/src/Orchard.Web/Core/Contents/Services/IContentDefinitionService.cs +++ b/src/Orchard.Web/Core/Contents/Services/IContentDefinitionService.cs @@ -5,7 +5,8 @@ namespace Orchard.Core.Contents.Services { public interface IContentDefinitionService : IDependency { IEnumerable GetTypeDefinitions(); ContentTypeDefinition GetTypeDefinition(string name); - void AddTypeDefinition(ContentTypeDefinitionStub contentTypeDefinition); + void AddTypeDefinition(ContentTypeDefinition contentTypeDefinition); + void AlterTypeDefinition(ContentTypeDefinition contentTypeDefinition); void RemoveTypeDefinition(string name); } } \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/Styles/admin.css b/src/Orchard.Web/Core/Contents/Styles/admin.css new file mode 100644 index 000000000..d6614bb1f --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Styles/admin.css @@ -0,0 +1,6 @@ +.contents #main h2 { + margin:1.5em 0 .5em; +} +.manage.add-to-type { + margin-top:-4em; +} \ No newline at end of file diff --git a/src/Orchard.Web/Core/Contents/ViewModels/EditTypeViewModel.cs b/src/Orchard.Web/Core/Contents/ViewModels/EditTypeViewModel.cs new file mode 100644 index 000000000..b43cc113b --- /dev/null +++ b/src/Orchard.Web/Core/Contents/ViewModels/EditTypeViewModel.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement.MetaData.Models; +using Orchard.Mvc.ViewModels; + +namespace Orchard.Core.Contents.ViewModels { + public class EditTypeViewModel : BaseViewModel { + public EditTypeViewModel() { + Settings = new SettingsDictionary(); + Parts = new List(); + } + public EditTypeViewModel(ContentTypeDefinition contentTypeDefinition) { + Name = contentTypeDefinition.Name; + DisplayName = contentTypeDefinition.DisplayName; + Settings = contentTypeDefinition.Settings; + Parts = contentTypeDefinition.Parts.Select(p => new EditTypePartViewModel(p)); + } + + public string Name { get; set; } + public string DisplayName { get; set; } + public SettingsDictionary Settings { get; set; } + public IEnumerable Parts { get; set; } + } + + public class EditTypePartViewModel { + public EditTypePartViewModel() { + Settings = new SettingsDictionary(); + } + public EditTypePartViewModel(ContentTypeDefinition.Part part) { + PartDefinition = new EditPartViewModel(part.PartDefinition); + Settings = part.Settings; + } + + public EditPartViewModel PartDefinition { get; set; } + public SettingsDictionary Settings { get; set; } + } + + public class EditPartViewModel { + public EditPartViewModel() { + Fields = new List(); + Settings = new SettingsDictionary(); + } + public EditPartViewModel(ContentPartDefinition contentPartDefinition) { + Name = contentPartDefinition.Name; + Fields = contentPartDefinition.Fields.Select(f => new EditPartFieldViewModel(f)); + Settings = contentPartDefinition.Settings; + } + + public string Name { get; set; } + public IEnumerable Fields { get; set; } + public SettingsDictionary Settings { get; set; } + } + + public class EditPartFieldViewModel { + public EditPartFieldViewModel() { + Settings = new SettingsDictionary(); + } + public EditPartFieldViewModel(ContentPartDefinition.Field field) { + Name = field.Name; + FieldDefinition = new EditFieldViewModel(field.FieldDefinition); + Settings = field.Settings; + } + + public string Name { get; set; } + public EditFieldViewModel FieldDefinition { get; set; } + public SettingsDictionary Settings { get; set; } + } + + public class EditFieldViewModel { + public EditFieldViewModel() { } + public EditFieldViewModel(ContentFieldDefinition contentFieldDefinition) { + Name = Name; + } + + public string Name { get; set; } + } +} diff --git a/src/Orchard.Web/Core/Contents/Views/Admin/EditType.ascx b/src/Orchard.Web/Core/Contents/Views/Admin/EditType.ascx new file mode 100644 index 000000000..ffce68bcf --- /dev/null +++ b/src/Orchard.Web/Core/Contents/Views/Admin/EditType.ascx @@ -0,0 +1,23 @@ +<%@ Control Language="C#" Inherits="Orchard.Mvc.ViewUserControl" %> +<%@ Import Namespace="Orchard.Core.Contents.ViewModels" %><% +Html.RegisterStyle("admin.css"); %> +

<%:Html.TitleForPage(T("Edit Content Type").ToString())%>

<% +using (Html.BeginFormAntiForgeryPost()) { %> + <%:Html.ValidationSummary() %> +
+ + <%:Html.TextBoxFor(m => m.DisplayName, new {@class = "textMedium"}) %> + + <%:Html.TextBoxFor(m => m.Name, new {@class = "textMedium"}) %> +
+ <%:Html.EditorFor(m => m.Settings) %> +

<%:T("Parts") %>

+
<%: Html.ActionLink(T("Add").Text, "AddPart", new { }, new { @class = "button" })%>
+ <%:Html.EditorFor(m => m.Parts, "Parts", "") %> +

<%:T("Fields") %>

+
<%: Html.ActionLink(T("Add").Text, "AddField", new { }, new { @class = "button" })%>
+ <%--<%:Html.EditorFor(m => m.Fields, "ContentTypeFields")%>--%> +
+ +
<% +} %> diff --git a/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/ContentTypeDefinition.ascx b/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/ContentTypeDefinition.ascx index 525247982..7e6b634ae 100644 --- a/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/ContentTypeDefinition.ascx +++ b/src/Orchard.Web/Core/Contents/Views/DisplayTemplates/ContentTypeDefinition.ascx @@ -6,7 +6,7 @@