Plone 4 : How to customize a method in Archetypes content types? - adapter

I have tried, under Plone 4.3.3, to customize a class method of an archetype content type in one of my products.
I have a product bsw.produit_1 with a content type MyContent defined as follows:
class MyContent(base.ATCTContent):
implements(IMyContent)
meta_type = "MyContent"
schema = MyContent`
def ma_fonction(self):
......
return res
I want to modify the code of my function ma_fonction in another product. I have tried using an adapter and following the plone docs, but without success.
The class where I wish to customize the function:
class CustomClass(object):
""" """
implements(IMyContent)
adapts(IMyContent)
def at_post_payment_script(self, obj_transaction):
""" """
......
# My new code
return res
The configure.zcml where I declared my adapter:
<adapter for="bsw.produit_1.content.mycontent.MyContent"
provides="bsw.produit_1.interfaces.IMyContent"
factory=".customclass.CustomClass" />
In my zcml declaration, I've also tried putting archetypes.schemaextender.interfaces.ISchemaExtender as provides or putting the interface IMyContent for for instead of the class.
None of these worked, every time, the customized code is not executed. Does anybody have a solution for this?

The solution you need depends on what you want to achieve.
But archetypes.schemaextender is the wrong solution.
schemaextender is there to modify the schema, this includes:
fields order
field/widget attributes
schemata
setter/getter of a field
new fields
override fields
To implement your own adaptera is definitely the right approach.
First you need to implement a adapter for the default behavior.
Second, you need to adapt the context and the request. The request is important, since that's a way to define a more specific adapter if your other product is installed.
Python code for the default implementation (adapter.py):
from zope.component import adapts
from zope.interface import Interface
from zope.interface import implements
class IBehavior(Interface):
def __init__(context, request)
"""Adapts context and request"""
# ... more ...
class DefaultBehavior(object):
implements(IBehavior)
adapts(IMyContent, Interface) # IMPORTAN two discriminators
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
# your default implementation goes here.
Register the adapter with zcml:
<adapter factory=".adapter.DefaultBehavior" />
Your now able to call the default adapter in ma_fonction
from zope.component import getMultiAdapter
class MyContent(base.ATCTContent)
def ma_fonction(self):
adapter = getMultiAdapter((self, self.REQUEST), IDefaultBehavior)
return adapter()
Now you can implement a more specific adapter in your other product using a browserlayer. Check documentation, how to register a browserlayer
In your otherpackage you can now register a adapter which implements the same IBehavior interface, but also adapts your browserlayer.
from other.package.interfaces import IOtherPackageLayer
from zope.component import adapts
from zope.interface import implements
class DifferenBehavior(object):
implements(IBehavior)
adapts(IMyContent, IOtherPackageLayer) # IMPORTAN adapt the browserlayer not Interface
def __init__(self, context, request):
self.context = context
self.request = request
def __call__(self):
# your different implementation goes here.
Register also with zcml:
<adapter factory=".adapter.DifferenBehavior" />
Your ma_fonctionnow calls the default adapter, if the other package is not installed. And the different adapter if the other package is installed.

The simplest method you can use (although not politically correct!) is monkey-patching.
Take a look at collective.monkeypatcher, you simply need a configuration like that (in your 3rd party product):
<monkey:patch
description=""
class="your.package.MyContent"
original="ma_fonction"
replacement=".monkeys.new_ma_fonction"
/>
Then in your package create also a monkeys.py module with the new method inside:
def new_ma_fonction(self):
# do stuff
return res

Related

Grails Data Service Cannot Use Regular Service

Happy Another Covid Day. When I use generate-all, Grails creates the Data Service for me. I begin to understand what a data service is.
I also have my own service for my Author and Book classes to use. I name my service ImportService. I have methods in the ImportService to clean up my book data read from a CSV file before the Data Service saves my books to the database. I also follow the instruction to make the Data Service an Abstract Class. So, I can put my own method in the Data Service.
Since the Author has its own AuthorService, and the Book has its own BookService, I want the different Data Service to access the method in my ImportService. So, I don't have to copy and paste the import CSV code multiple times. So, I put the line ImportService importService in the AuthorServie class and the BookService class. That does not go well. importService is always NULL inside the Data Service classes. I google the problem. They say I cannot inject another service to the grails.gorm.services.Service.
There is a post that says to make a bean. I am new to Grails. I have no idea what they are talking about even with the codes posted. Part of my background is Assembly Language, C, and Pascal. My head is filled with lingo like Top Down, Subroutine, library, Address, and Pointer. I have no idea what a Bean is.
This is what it is. I am wondering whether this is a bug or by design that you cannot inject a service to the gorm service.
Thanks for your "Pointer".
See the project at https://github.com/jeffbrown/tom6502servicedi. That project uses Grails 4.0.3 and GORM 7.0.7.
https://github.com/jeffbrown/tom6502servicedi/blob/main/grails-app/services/tom6502servicedi/ImportService.groovy
package tom6502servicedi
class ImportService {
int getSomeNumber() {
42
}
}
https://github.com/jeffbrown/tom6502servicedi/blob/917c51ee173e7bb6844ca7d40ced5afbb8d9063f/grails-app/services/tom6502servicedi/AuthorService.groovy
package tom6502servicedi
import grails.gorm.services.Service
import org.springframework.beans.factory.annotation.Autowired
#Service(Author)
abstract class AuthorService {
#Autowired
ImportService importService
// ...
int getSomeNumberFromImportService() {
importService.someNumber
}
}
https://github.com/jeffbrown/tom6502servicedi/blob/917c51ee173e7bb6844ca7d40ced5afbb8d9063f/grails-app/controllers/tom6502servicedi/AuthorController.groovy
package tom6502servicedi
import grails.validation.ValidationException
import static org.springframework.http.HttpStatus.*
class AuthorController {
AuthorService authorService
// ...
def someNumber() {
render "The Number Is ${authorService.someNumberFromImportService}"
}
}
Sending a request to that someNumber action will verify that the ImportService is injected into the AuthorService and the AuthorService is injected into the AuthorController.
$ curl http://localhost:8080/author/someNumber
The Number Is 42

My code using user_data_dir does not work and it gives an error saying 'self' is not defined. Can someone explain to me why?

The following is a snippet of my code. When I tried running this, the error states that 'self' is not defined. I copied this code online because I don't really know how to use the function user_data_dir. I know that it works (I'm using Windows to write this) with just store = JsonStore('user.json') but I've been reading that using the function user_data_dir would be useful because it is a general function that creates a writeable path for various systems. Would be great if someone could help explain this!
from kivy.storage.jsonstore import JsonStore
from os.path import join
data_dir = getattr(self, 'user_data_dir')
store = JsonStore(join(data_dir,'user.json'))
class Welcome(Screen):
pass
data_dir = getattr(self, 'user_data_dir')
When you copied this line, it was somewhere inside some class's function:
class Some:
def func(self):
data_dir = getattr(self, 'user_data_dir')
Method located inside class in Python receives self as first parameter.
But not every object has user_data_dir attribute: as inclement noticed above it's App objects attribute. You should do something like:
class MyApp(App):
def build(self):
data_dir = getattr(self, 'user_data_dir')
store = JsonStore(join(data_dir,'user.json'))
# ...
Upd:
You can store path to json file inside app class and access to app instance to get this path with App.get_running_app():
class MyApp(App):
#property # see https://www.programiz.com/python-programming/property
def storage(self):
return join(self.user_data_dir, 'user.json')
and later in any place you want:
class SomeClass():
def some_func(self):
print('here\'s our storage:', App.get_running_app().storage)

Google guice, override configuration

I'm working with Guice and have one design question. My App consists of few module:
myapp-persistence (JPA Entities, DAO, other DB related stuff)
myapp-backend (Some background daemons, they use myapp-persistence )
myapp-rest (REST app that depends on myapp-persistence)
myapp-persistence must have singleton HibernateSessionFactory. It's by Hibernate design.
No problem I can solve it with Guice:
class MyAppPersistenceModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[SomeStuff])
bind(classOf[ClientDao])
bind(classOf[CustomerDao])
bind(classOf[SessionFactory]).toProvider(classOf[HibernateSessionFactoryProvider]).asEagerSingleton()
}
#Provides
def provideDatabaseConnectionConfiguration: DatabaseConnectionConfiguration = {
DatabaseConnectionConfiguration.fromSysEnv
}
}
The problem with passing DatabaseConnectionConfiguration to that singleton. myapp-persistence module doesn't really care how to get that config. Right now it's taken from sys variables.
myapp-rest is play-app and it wants to read conf from application.conf and inject it into other components using Guice.
myapp-backend does more or less the same.
Right now I'm locked myself with
#Provides
def provideDatabaseConnectionConfiguration: DatabaseConnectionConfiguration = {
DatabaseConnectionConfiguration.fromSysEnv
}
And I don't understand how to make it flexible and configurable for myapp-rest and myapp-backend.
UPD
According to answer, I did it this way:
Defined trait
trait DbConfProvider {
def dbConf: DbConf
}
Singleton factory now depends on provider:
class HibernateSessionFactoryProvider #Inject()(dbConfProvider: DbConfProvider) extends Provider[SessionFactory] {
}
myapp-persistence module exposes public guice module with all piblic persistence module DAO.
myapp-persistence has module used only for testing purposes. myapp-persistence Injector load module described below:
class MyAppPersistenceDbConfModule extends AbstractModule {
override def configure(): Unit = {
bind(classOf[DbConfProvider]).to(classOf[DbConfSysEnvProvider])
}
}
DbConfSysEnvProvider reads DB connection settings from sys env. Non production use case.
Play app has it's own conf mechanism. I've added my custom module to app conf:
# play-specific config
play.modules.enabled += "common.components.MyAppPersistenceDbConfModule"
# public components from myapp-persistence module.
play.modules.enabled += "com.myapp.persistence.connection.PersistenceModule"
And my configuration service:
#Singleton
class ConfigurationService #Inject()(configuration: Configuration) extends DbConfProvider {
...}
I am not an expert on Play-specific setup, but generally this kind of design problem is solved in one of the following ways:
No default. Remove the binding of DatabaseConnectionConfiguration from the upstream module (myapp-persistence), and define it in each downstream module (myapp-backend, myapp-rest) as appropriate.
Default with override. Keep the default binding of DatabaseConnectionConfiguration like you did, implementing the most common configuration strategy there. Override it in downstream modules using Guice Modules.override(..) API when needed.
Implement a unified configuration mechanism across the modules, that does not depend on particular frameworks used. (E.g. Bootique, which is built on Guice ... Haven't used it with Play though).
I personally prefer the approach #3, but in the absence of something like Bootique, #2 is a good substitute.

How do I change the API name in the class generated by swagger-codegen

I am using swagger-springmvc and swagger-codegen to generate a Java client library for a RESTful webservice. I have written my own extension of the BasicJavaGenerator (see below) to override the package names, and can successfully generate the client library files. The "main" files generated are:
swagger-codegen/generated-code/java/pom.xml
swagger-codegen/generated-code/java/src/main/java/com/example/ApiApi.java
swagger-codegen/generated-code/java/src/main/java/com/example/model/*.java
What I can't find is how to set the name of my API to get the code generator to rename ApiApi.java to MyProjectApi.java (for example) as seems to be done in the samples included in with swagger-codegen. I have tried looking at the code generator code to override the api name, and have also tried looking both at the swagger json spec and swagger springmvc functionality to an option to set the name.
The code generator:
package com.wordnik.swagger.codegen
import com.wordnik.swagger.codegen.BasicJavaGenerator
object MyJavaGenerator extends BasicJavaGenerator {
def main(args: Array[String]) = generateClient(args)
// api invoker package
override def invokerPackage = Some("com.example.api")
// package for models
override def modelPackage = Some("com.example.api.model")
// package for api classes
override def apiPackage = Some("com.example.api")
}
you can override this behavior as such:
override def toApiName(name: String) = "MyProject" + name
as you see fit. Please note that you should consider upgrading to 2.1.0-SNAPSHOT, which lives in https://github.com/swagger-api/swagger-codegen/tree/develop_2.0
In addition to the accepted answer - in order to get the capitalization right, you can use:
override def toApiName(name: String) = "MyProject" + name.capitalize

Scalatest sharing service instance across multiple test suites

I have FlatSpec test classes which need to make use of a REST service for some fixture data. When running all the tests in one go I only really want to instantiate the REST client once as it may be quite expensive. How can I go about this and can I also get it to work for running just one test class when I am running in my IDE?
1. Use mocking:
I would advice you to use some kind of mocking when you try to test REST service. You can try for example scala-mock. Creation of mock service isn't time/cpu consuming, so you can create mocks in all your tests and don't need to share them.
Look:
trait MyRestService {
def get(): String
}
class MyOtherService(val myRestService: MyRestService) {
def addParentheses = s"""(${myRestService.getClass()})"""
}
import org.scalamock.scalatest.MockFactory
class MySpec extends FreeSpec with MockFactory {
"test1 " in {
// create mock rest service and define it's behaviour
val myRestService = mock[MyRestService]
val myOtherService = new MyOtherService(myRestService)
inAnyOrder {
(myRestService.get _).expects().returning("ABC")
}
//now it's ready, you can use it
assert(myOtherService.addParentheses === "(ABC)")
}
}
2. Or use Sharing fixtures:
If you still want to use real implementation of you REST service and create only one instance and then share it with some test condider using:
get-fixture methods => Use it when you need the same mutable fixture objects in multiple tests, and don't need to clean up after.
withFixture(OneArgTest) => Use when all or most tests need the same fixtures that must be cleaned up afterwords.
Refer to http://www.scalatest.org/user_guide/sharing_fixtures#loanFixtureMethods for more details and code examples.
If you want to share the same fixture against multiple Suites use org.scalatest.Suites and #DoNotDiscover annotation (these requires at least scalatest-2.0.RC1)
Pawel's last comment fits well.
It was easier by inheriting from Suite with BeforaAndAfterAll instead of Suites.
import com.typesafe.config.ConfigFactory
import com.google.inject.Guice
import org.scalatest.{BeforeAndAfterAll, Suite}
import net.codingwell.scalaguice.InjectorExtensions.ScalaInjector
class EndToEndSuite extends Suite with BeforeAndAfterAll {
private val injector = {
val config = ConfigFactory.load
val module = new AppModule(config) // your module here
new ScalaInjector(Guice.createInjector(module))
}
override def afterAll {
// your shutdown if needed
}
override val nestedSuites = collection.immutable.IndexedSeq(
injector.instance[YourTest1],
injector.instance[YourTest2] //...
)
}

Resources